Wekan logo

Wekan

Open source kanban board application built with Meteor

Alternative to: trello


About Versions (100)

v9.70

2026-06-23

v9.70 2026-06-23 WeKan ® release

This release fixes the following bugs:

  • Copying a card to or from a board with no labels threw. Card.copy()’s cross-board label-remap did oldBoard.labels.filter(...) / filterCopiedLabelIds(newBoard.labels, …) without guarding a missing labels array. Most boards always have default labels, but a board created via the REST API (POST /api/boards) has no labels array — so copying a card to/from such a board (or copying the board itself, which copies its cards) threw Cannot read properties of undefined (reading 'filter') and returned HTTP 500. Both label lookups now fall back to []. Cross-board copy regressions in tests/playwright/specs/17-rest-api.e2e.js.
  • Copying a board did not copy its webhooks, #5592: board.copy() duplicated swimlanes/lists/cards, custom fields and rules/triggers/actions, but had no loop for Integrations (outgoing webhooks), so a copied board lost all of them (even though they are per-board children — boardRemover deletes them by boardId). copy() now also copies the board’s Integrations, remapping boardId (URL/token/activities carry over — the copying user is a board admin with access to them). Regression in tests/playwright/specs/17-rest-api.e2e.js.
  • Disabled user accounts could be added to boards, #1894: none of the add-member paths checked the target account’s loginDisabled flag, so a deactivated user could be invited/added and assigned to cards. inviteUserToBoard now throws error-user-disabled, and the REST POST /api/boards/:boardId/members/:userId/add endpoint returns HTTP 400, when the target account is disabled (re-enabling the account allows the add again). Regression in tests/playwright/specs/17-rest-api.e2e.js.
  • Admin Panel boards report listed removed members as current members, #5122: removing a member from a board marks the member entry isActive: false (it is kept in board.members for role history / re-activation), but the Admin Panel → Reports → Boards member column listed all member entries, so removed users still appeared as members. The report now filters to active members (isActive !== false). (The raw GET /api/boards/:boardId response intentionally still returns the full members array with each entry’s isActive flag, so API consumers can filter as they need.)
  • Could not remove a deleted user from a card’s members, #4847 (card side): when a user account is deleted, its entry stays in a card’s members, rendering as a blank avatar. Clicking it opened the member popup, whose template dereferenced the now-missing user document (user.profile.fullname / user.username) and failed to render — so there was no way to remove the orphaned member. The popup now detects a missing user, shows a “Deleted user” entry with the raw id, and keeps the Remove from Card control (the remove handler keys off the member’s userId, not the user document), so orphaned card members can be removed. (No automated regression — a self-evident Blaze template guard; the underlying unassignMember removal already worked. The board-members list still hides such entries because activeMembers() also intentionally filters members whose user doc is merely not-yet-loaded, so surfacing board-side orphans cleanly is a separate follow-up.)
  • A newly added board member was missing from the card members popup, #4965: the card “add members” popup snapshotted the board’s member list once when it opened, so a member added to the board afterwards (or whose user document finished loading just after the popup opened) did not appear until the popup was reopened. The popup now derives its candidate list reactively (it stores only the filter term and re-reads board.activeMembers() on each render), so newly-added members show up without reopening. No change to activeMembers() itself (its deleted-user filtering is unchanged).
  • Editing a linked card you cannot write to failed silently, #5809: a linked card whose target lives on a board the user cannot write to (e.g. a private board) rejected title/description edits at the server allow rule, but the card-detail edit handlers had no error handling, so the edit just vanished with no feedback. The title and description submit handlers now catch the failure and show the error (mirroring the existing label-color handler), so the user sees why the edit did not save. (No automated regression — surfacing a permission denial across a private linked board is a UX/error path that is not cleanly reproducible in the test harness.)
  • Performance: copying a card with many checklist items took minutes (#5688). Checklist.copy() duplicated each checklist item with the hooked insertAsync, so collection-hooks fired each item’s after.insert — every one doing a getCard and inserting an addChecklistItem activity. Copying a card with ~100 items meant ~100+ activity inserts (plus per-item DB round-trips), taking minutes and spiking CPU. The copy now uses .direct for the checklist and its items (skipping the per-item activity hooks — pure churn for a wholesale copy) and sets boardId itself, the same approach as the cardRemover delete path. Copy regression (checklist + all items duplicated) in tests/playwright/specs/17-rest-api.e2e.js.
  • Copying a card to another board orphaned its subtasks on the old board (#5347 data symptom): Card.copy() re-pointed each subtask’s parentId to the copied card but left its boardId/swimlaneId/listId pointing at the source board — so a cross-board copy created subtasks stranded on the original board whose parent lives on another board (and it mutated the cached source docs in place). Copied subtasks are now re-homed onto the destination board/swimlane/list alongside the copied parent, via a fresh object (no cache mutation). Cross-board copy regression in tests/playwright/specs/17-rest-api.e2e.js. (This fixes the orphaned-cross-board-subtask data symptom noted in #5347; the separate “Maximum call stack” error there is not yet root-caused — see the issue.)
  • Error when clicking the notification icon, #5325: a notification whose referenced activity no longer existed (its card/board was deleted) left an entry whose activityObj was null, and the notifications drawer dereferences it (activity.user, activity._id, …) — so one orphaned notification threw and broke the whole popup. user.notifications() now drops entries whose activity can’t be resolved. (No automated regression — an orphaned-notification state is not cleanly reproducible in the harness; the fix is a self-evident filter.)
  • Deleting a custom field from a board could throw, #5390: removing a (multi-board) custom field from one board runs a before.update hook that logged a setCustomField activity by reading (await getActivity({customFieldId})).value — with no null guard, so a field that never had a value set (no such activity) threw Cannot read properties of undefined and aborted the removal. The lookup is now guarded. (server/models/customFields.js)
  • Board “show checklists on minicard” setting had no effect, #5565: the sidebar toggle writes board.allowsChecklistsOnMinicard, but the minicard render checked a different, UI-less field (board.allowsChecklistAtMinicard), so enabling the board-wide setting never showed checklists on minicards. The minicard now reads the field the toggle actually sets. (client/components/cards/minicard.js)

and these issues are verified resolved in current code (could not reproduce / no error observed here; re-test on the reporter’s data requested):

  • #5388 (collapsing a list affected all users): list collapse state is now stored per user (profile.collapsedLists[boardId][listId], or a cookie for logged-out users) instead of a shared collapsed field on the list document — so one user collapsing a list no longer changes it for everyone (resolved by commit 414b8dbf4, which postdates the report; mirrors the per-user swimlane-collapse handling).
  • #3894 (board import failed when the JSON’s members referenced a user not present in users): the importers already guard a missing user entry (skip the dangling member instead of dereferencing undefined) in client/components/import/wekanMembersMapper.js, models/wekanmapper.js and models/wekanCreator.js, with a dedicated test (tests/wekanCreator.inconsistent.test.js).
  • #5411 (non-super-admin board admins could not see the add-member ”+”): the sidebar add-member button is gated on currentUser.canInviteToBoard, which returns true for any board admin (board.hasAdmin), and the secure default invite roles include board-admin. So board admins (not just site admins) see the ”+”. This was resolved by the “Allow Invite to Board” roles feature (commit c956ab5a4), which postdates the report; a site admin can additionally let other board roles invite via Admin Panel → People → Roles.
  • #5627 (rules not copied when creating a board from a template): board.copy() already copies the board’s rules + triggers + actions (remapping boardId and the rule’s triggerId/actionId); the report predates that code. Now covered by the #5592/#5627 copy regression in tests/playwright/specs/17-rest-api.e2e.js so it cannot silently regress.
  • #5630 (cannot save Admin Panel Layout settings): already fixed — the Layout save handler had referenced form fields that were moved to the separate Accessibility settings template, throwing before the save; the current js-save-layout handler no longer reads those fields.
  • #5117 (a TeX formula rendered both an SVG and a <math> tag): already fixed by migrating math rendering from markdown-it-mathjax3 (which emitted both SVG and MathML) to Temml, which outputs MathML only (packages/markdown/src/template-integration.js).