wekan/docs/Security/PerUserDataAudit2025-12-23/QUICK_REFERENCE.md
2025-12-23 08:01:30 +02:00

8 KiB

Wekan Persistence Improvements - Quick Reference

What Was Changed?

Removed

  • Board-level collapsed field from Swimlanes
  • Board-level collapsed field from Lists
  • REST API endpoint for updating list collapsed status
  • collapse() mutation from Swimlanes

Added

  • Per-user position history with undo/redo
  • LocalStorage validation and cleanup
  • SwimlaneId validation migration
  • Checkpoint/savepoint system for position history
  • Enhanced data validation for all UI preferences

How It Works

Per-User Settings (Your Preferences)

These are NOW per-user and persisted:

  • Swimlane collapse state
  • List collapse state
  • List width
  • Swimlane height

Where it's stored:

  • Logged-in users: user.profile
  • Non-logged-in users: Browser localStorage
  • Validated & cleaned automatically

Position History (Card Movements)

Every time you move a card:

  • Automatically tracked in userPositionHistory collection
  • Stored with previous and new position
  • Can be undone with Meteor.call('userPositionHistory.undo', historyId)
  • Checkpoints can be created with Meteor.call('userPositionHistory.createCheckpoint', boardId, name)

Data Validation

All UI preference data is validated:

  • List widths: 100-1000 pixels
  • Swimlane heights: -1 (auto) or 50-2000 pixels
  • Corrupted data: automatically removed
  • Invalid data: rejected on write

For Users

What Changed?

  • Your collapse preferences are now private to you (not shared with others)
  • They persist across page reloads
  • They work even if not logged in (saved in browser)
  • Invalid data is automatically cleaned up

What You Can Do (Coming Soon)

  • Undo/redo card movements
  • Create savepoints of board state
  • Restore to previous savepoints
  • Use Ctrl+Z to undo

For Developers

New Collections

UserPositionHistory

{
  userId: String,
  boardId: String,
  entityType: 'card' | 'list' | 'swimlane' | 'checklist' | 'checklistItem',
  entityId: String,
  actionType: 'move' | 'create' | 'delete',
  previousState: Object,
  newState: Object,
  isCheckpoint: Boolean,
  checkpointName: String,
  createdAt: Date
}

New Meteor Methods

// Create a checkpoint
Meteor.call('userPositionHistory.createCheckpoint', boardId, 'name');

// Undo a change
Meteor.call('userPositionHistory.undo', historyId);

// Get recent history
Meteor.call('userPositionHistory.getRecent', boardId, 50, (err, result) => {
  // result is array of history entries
});

// Get checkpoints
Meteor.call('userPositionHistory.getCheckpoints', boardId, (err, checkpoints) => {
  // result is array of checkpoints
});

// Restore to checkpoint
Meteor.call('userPositionHistory.restoreToCheckpoint', checkpointId);

Updated Models

cards.js

  • move() now automatically tracks changes
  • Uses UserPositionHistory.trackChange()

swimlanes.js

  • collapsed field removed (use profile.collapsedSwimlanes)
  • collapse() mutation removed

lists.js

  • collapsed field removed (use profile.collapsedLists)
  • Removed from REST API

users.js

  • Enhanced getListWidthFromStorage() with validation
  • Enhanced setSwimlaneHeightToStorage() with validation
  • Added automatic cleanup of invalid data

New Files

client/lib/localStorageValidator.js
  - validateAndCleanLocalStorage()
  - shouldRunCleanup()
  - getValidatedLocalStorageData()
  - setValidatedLocalStorageData()
  - validators object with all validation functions

models/lib/userStorageHelpers.js
  - getValidatedNumber()
  - setValidatedNumber()
  - getValidatedBoolean()
  - setValidatedBoolean()

models/userPositionHistory.js
  - UserPositionHistory collection
  - Helpers: getDescription(), canUndo(), undo()
  - Meteor methods for interaction

server/migrations/ensureValidSwimlaneIds.js
  - Runs automatically on startup
  - Fixes cards/lists without swimlaneId
  - Rescues orphaned data

Migration Details

Automatic Migration: ensureValidSwimlaneIds

Runs on server startup:

  1. Finds cards without swimlaneId

    • Assigns them to default swimlane
  2. Finds orphaned cards

    • SwimlaneId points to deleted swimlane
    • Moves them to "Rescued Data" swimlane
  3. Adds validation hooks

    • Prevents swimlaneId removal
    • Auto-assigns on card creation

Tracking:

Migrations.findOne({ name: 'ensure-valid-swimlane-ids' })
// Shows results of migration

Data Examples

Before (Broken)

// Swimlane with board-level collapse
{
  _id: 'swim123',
  title: 'Development',
  collapsed: true  // ❌ Shared with all users!
}

// Card without swimlaneId
{
  _id: 'card456',
  title: 'Fix bug',
  swimlaneId: undefined  // ❌ No swimlane!
}

After (Fixed)

// Swimlane - no collapsed field
{
  _id: 'swim123',
  title: 'Development',
  // collapsed: removed ✅
}

// User's profile - has per-user settings
{
  _id: 'user789',
  profile: {
    collapsedSwimlanes: {
      'board123': {
        'swim123': true  // ✅ Per-user!
      }
    },
    listWidths: {
      'board123': {
        'list456': 300  // ✅ Per-user!
      }
    }
  }
}

// Card with swimlaneId
{
  _id: 'card456',
  title: 'Fix bug',
  swimlaneId: 'swim123'  // ✅ Always set!
}

// Position history entry
{
  _id: 'hist789',
  userId: 'user789',
  boardId: 'board123',
  entityType: 'card',
  entityId: 'card456',
  actionType: 'move',
  previousState: { swimlaneId: 'swim123', listId: 'list456', sort: 1 },
  newState: { swimlaneId: 'swim123', listId: 'list789', sort: 2 },
  createdAt: ISODate('2025-12-23T07:00:00Z')
}

Troubleshooting

Q: My collapse state isn't persisting

A: Make sure you're using the new per-user settings methods:

user.setCollapsedSwimlane(boardId, swimlaneId, true);
user.getCollapsedSwimlaneFromStorage(boardId, swimlaneId);

Q: I see "Rescued Data" swimlane with orphaned cards

A: Migration found cards pointing to deleted swimlanes. They're safe in the rescue swimlane. You can move them to proper swimlanes.

Q: localStorage is being cleared

A: That's intentional - we only keep valid data. Invalid/corrupted data is removed automatically during daily cleanup.

Q: How do I create a checkpoint?

A: Use the Meteor method:

Meteor.call('userPositionHistory.createCheckpoint', boardId, 'Before big changes');

Q: How do I undo a card move?

A: Use the Meteor method:

Meteor.call('userPositionHistory.undo', historyEntryId);

Performance Notes

Storage

  • localStorage: Max 50 boards, max 100 items per board
  • UserPositionHistory: Max 1000 entries per user per board
  • Auto-cleanup: Runs daily

Queries

  • Limited to 100 results per query
  • Indexed by userId, boardId, createdAt
  • Fast checkpoint retrieval

Validation

  • Runs on startup (once per day)
  • Only validates if needed
  • Removes excess data automatically

What's Next?

Coming Soon

  • Undo/redo buttons in UI
  • History sidebar
  • Keyboard shortcuts (Ctrl+Z)
  • Checkpoint UI

Future

  • Field-level history (description, comments)
  • Search across historical values
  • Visual timeline
  • Collaborative undo

Files to Know

File Purpose
models/userPositionHistory.js Position history collection
client/lib/localStorageValidator.js Data validation
server/migrations/ensureValidSwimlaneIds.js Automatic migration
models/swimlanes.js Swimlane model
models/lists.js List model
models/cards.js Card model with tracking

Questions?

See detailed documentation:


Status: Ready for use
Last Updated: 2025-12-23