github wekan/wekan v9.69

latest releases: v12.21, v12.20, v12.19...
3 months ago

v9.69 2026-06-23 WeKan ® release

This release fixes the following bugs:

  • Can't edit a card's members in the UI after removing them via the REST API,
    #3697: clearing a card's members (or assignees) over REST left the
    field uneditable in the UI. The card PUT handler guarded with if (req.body.members) — falsy for the two natural
    clear payloads (null and "") — so "remove the last member" was a silent no-op, and its string branch was written
    to store null rather than []; a card with members: null then breaks UI code that treats it as an array. The
    handler now uses an !== undefined guard plus a shared coercion helper so any clear payload (null / "" / [])
    stores a clean String[] (never null), a single id is wrapped into an array, and stray non-string entries are
    dropped; getMembers()/getAssignees() also coerce a legacy null to [] on read so existing corrupted documents
    edit cleanly. Pure logic in models/lib/restArrayParam.js with a Meteor-free Node unit test
    (tests/restArrayParam.test.cjs, npm run test:unit:node, incl. a negative test reproducing the old
    null-writing logic), plus a REST regression in tests/playwright/specs/17-rest-api.e2e.js.
  • Can't create a card with no member via the REST API,
    #2875: the card-create endpoints (POST .../cards and
    POST .../cards/bulk) wrote req.body.members/assignees straight to the insert with no normalization — the
    create-side twin of #3697 — so a null/"" payload persisted as null (breaking later UI editing). Both handlers
    now run the same coerceRestArrayParam helper: the field is omitted when not provided (so the schema default []
    applies), any clear payload becomes [] (never null), and a single id is wrapped into an array. REST regression in
    tests/playwright/specs/17-rest-api.e2e.js.
  • Board created through the REST API shows in the API but not in the browser UI,
    #5650: POST /api/boards set the board's sole member's userId from
    req.body.owner with no fallback, so a request that omits owner created a board whose only member had
    userId: undefined. The board-list publication matches members.$elemMatch: { userId, isActive: true }, which can
    never match an undefined id — so the board was returned by the REST API but invisible in the browser. owner now
    falls back to the authenticated caller (req.body.owner || req.userId), mirroring the Meteor create method. REST +
    DB regression in tests/playwright/specs/17-rest-api.e2e.js.
  • Copying a card to another board left its comments on the wrong board,
    #5166: CardComments.copy() cloned a comment but only changed its
    cardId, so a card copied to another board produced comments that kept the source board's boardId. Comment
    permission checks key off the comment's boardId, so edit/delete on a copied comment was validated against the wrong
    board, and any board-scoped query saw it on the old board. copy() now also sets the destination boardId (the
    author userId is intentionally preserved). Cross-board copy regression in
    tests/playwright/specs/17-rest-api.e2e.js. Note: the related "wrong author shown" symptom is a separate display
    issue — when a copied comment's author is not a member of the destination board, their user document isn't
    published there, so the UI can't resolve the name; adding those users to the board resolves it. A broader fix
    (publishing comment authors' minimal profile on boards where their comments appear) is left as a follow-up.
  • Exception "Removed nonexistent document" when deleting a card detail,
    #3252 (partial): deleting a comment or checklist/checklist-item could
    throw Removed nonexistent document on the client. The delete handlers called Collection.remove(_id) directly, but
    under heavy archive/delete churn the target doc can already have been evicted from the client's Minimongo cache, and
    removing a missing _id throws. The comment / checklist / checklist-item delete handlers now check the doc still
    exists in the local cache before removing it
    (client/components/activities/comments.js, client/components/cards/checklists.js). The server-side
    before.remove hooks were already hardened to guard + log instead of throwing. (No automated regression for the
    thrown exception — the eviction race is not deterministically reproducible; the guard itself is a self-evident
    findOne-then-remove.) The high CPU on bulk delete the issue also reports is reduced by the cascade change below;
    the remaining cost is on the archive path (a bulk update, not a delete) and is a separate follow-up.
  • Performance: deleting a card no longer fans out per-child activity churn (#3252,
    #5322). cardRemover removed a card's checklist items, checklists and
    comments with the hooked removeAsync, so collection-hooks fired each child's before.remove once per document —
    every one doing a getCard and inserting an activity (e.g. a removedChecklistItem per item), which then triggered
    the notification + publication observers. Deleting (or bulk-deleting) a card with many children produced an
    activity-insert storm and pegged the CPU. cardRemover now removes those children with .direct (skipping the
    per-child hooks — they only logged activities that are unviewable once the card is gone) and clears all of the
    card's activities in a single bulk op, which also removes the activities that a delete previously left orphaned. The
    deleteCard activity is still logged afterward (so the outgoing webhook fires), and attachments still go through the
    normal remove so their files are deleted. Cascade + activity-cleanup regression (also a negative test) in
    tests/playwright/specs/17-rest-api.e2e.js.

and this issue is verified resolved in current code (could not reproduce / no error observed here; re-test on the
reporter's data requested):

  • #2292 (archiving a swimlane appeared to delete all its cards):
    Swimlane.archive() (models/swimlanes.js) only sets archived: true on the swimlane — it
    does not touch or delete the cards, and restore() brings the swimlane and its cards back. While a swimlane is
    archived its cards are merely hidden from the board view (board queries filter to archived: false swimlanes), not
    lost — the v2.27-era cascade described in the report no longer happens. (A dedicated way to view an archived
    swimlane's cards before restoring would be a separate UX improvement.)

Don't miss a new wekan release

NewReleases is sending notifications on new releases.