Wekan logo

Wekan

Open source kanban board application built with Meteor

Alternative to: trello


About Versions (218)

v10.53

2026-07-30

v10.53 2026-07-31 WeKan ® release

This release fixes the following bugs:

The card window opens beside its card again, on whichever side has more room. Thanks to csonkaoszimt, Mintyt and xet7.

“notice how the card info docks neatly right next to the card” — the 6.09 behaviour asked for in #6465. The window had been moved off the middle of the board and docked to the end edge instead, which stopped it covering the board but left it nowhere near the card it belongs to: open a card in the FIRST list of a wide board and its details are a screen away, with the whole board between them.

It now opens beside the minicard it was opened from, on whichever side has more room, and always entirely inside the visible area. A card on the left of the board opens to its right; a card on the right opens to its left.

This is horizontal only. The vertical geometry — the staggered top and the bottom: 8px that makes the window full height — is already right, so nothing here writes top, bottom or height, and a test fails if it starts to.

The width is passed through unchanged unless the viewport itself is narrower than the window. Shrinking the window to fit the gap beside the minicard was tried and dropped: on a 600px-wide desktop it turned a 520px window into a 240px one, and this is about where the window is, not how big it is. When neither side has room it overlaps the card instead, pushed as far towards the roomier side as the viewport allows — the card is behind it for a moment, which is recoverable; a window off the edge of the screen is not.

Nothing is placed unless there is something to place it against: a card opened from a URL, from search, or whose list is scrolled out of view has no minicard on screen and keeps the previous dock. Only the desktop floating window is touched — the popup form (Board Table, search results) and the mini-screen card have their own geometry, and the maximized window’s insets are !important. A window the user has dragged keeps where they put it; the viewport clamp still applies to it, so a browser window that shrinks cannot leave it hanging off the edge.

One vertical side effect had to be paid for. The stylesheet’s default-position rule is :not([style*="left"]):not([style*="top"]), so writing an inline left switches it off — and it carried the top as well as the width. Without a replacement, a sixth open window (past the five staggered rules, reachable with “Open many cards at once”) would have had no top at all. The replacement is vertical-only and written BEFORE the stagger rules, so cards 1–5 still take their staggered top from those and no vertical position changes anywhere; the test pins that order, because the same rule written after them would win the specificity tie and flatten cards 2–5 onto card 1.

Propagate Members To Boards now adds the members when it is ticked, instead of only at the next LDAP sync. Thanks to ChristianMa97 and xet7.

Ticking “Propagate Members To Boards” for a team in Admin Panel > People > Teams stored the flag and added nobody. The members appeared later, when the LDAP sync cron ran the same propagation by its other, correct route — which is why the feature looked half-working rather than broken.

setTeamPropagateMembersToBoards(team, value) receives team as the selector the client sent — { _id: … }, the same value it hands to Team.updateAsync on the line above — and passed it whole to propagateGroupMembersToBoards, which put it straight into the member lookup. So it ran

Meteor.users.find({ 'teams.teamId': { _id: 'abc' } })

comparing a string field against an object. It matched nobody, the function returned “0 boards updated, 0 members added”, and nothing threw: the checkbox went green and did nothing. The org column beside it was identical and is fixed with it, though the report was about teams.

Both call sites pass the id now, and propagateGroupMembersToBoards normalises whatever it is given at the one point every caller goes through — a plain id, or a document or selector carrying one. Anything it cannot turn into an id is refused and logged, rather than quietly treated as a group that happens to have no members, because that silence is exactly how this survived a release. A missing group stays quiet, since the propagate-everything pass may legitimately have nothing to pass.

The select-all checkbox in the column header makes the same promise to the admin, and it did not propagate at all — not even wrongly: it set the flag on every row and stopped. It propagates now when that field is the one being turned on, and only for its own kind, so ticking the team column does not act on the org column beside it. The propagate-everything pass takes an optional kind for this; its default is unchanged, so the LDAP cron and the propagateOrgTeamMembersToBoards method still do both.

The two wiring tests required the buggy calls — they asserted propagateGroupMembersToBoards('team', team) verbatim, pinning that a call happened without asking whether its argument was right, and so held the bug in place. They require the id now and fail if the whole selector comes back, and the normaliser is exercised for real rather than read: every shape a caller might hold an id in resolves to it, a selector with no id resolves to nothing and warns, and an absent group is quiet.

Moving a card to another board no longer blanks every custom field id. Thanks to ChristianMa97 and xet7.

The values survived the move; their keys did not. Every entry came out keyed by an empty _id, which no board can match to a field definition, so the card arrived on the destination board with every custom field showing empty. Drag-and-drop, the Move card popup and the REST API newBoardId move all go through the same helper, and a cross-board card copy had it too.

mapCustomFieldsToBoard() was synchronous and called ReactiveCache.getCustomField(), which is async on the server — it awaits findOneAsync — and synchronous only on the client. So on the server both lookups returned Promises. A Promise is truthy, so the “field not found” guard never fired and the “the destination board has its own definition” branch always did, assigning newCf._id: undefined on a Promise. The schema declares customFields.$._id as optional: true, defaultValue: '', so collection2 cleaned that undefined to '' on the way to the database. Nothing threw and nothing was logged.

Both lookups are awaited now, and so is addBoard() — also async, and until the two above were awaited its branch was unreachable, so its own missing await had never been exercised.

Two more things in the same function. The entries are rebuilt rather than mutated in place: a card copy deliberately works on a shallow copy of the card “to avoid mutating the source card in ReactiveCache”, and cf._id = newCf._id reached straight through that copy into the source card’s own entry objects, re-keying the card being copied from. And an entry with no id is passed through instead of looked up — getCustomField() defaults its selector to {}, so a lookup of an empty id returns an arbitrary custom field, and a blank entry (including one blanked by this very bug) would have been re-keyed to whichever field came first.

This is the same defect as the fix for #6504 — an unawaited ReactiveCache.getBoard in this same cross-board branch, which gave “newBoard.getNextCardNumber is not a function” — one line further down. The guard added then named the two getBoard calls and stopped there, so the third call shipped broken for twenty more releases. That guard now checks the branch as a whole and fails on any unawaited async call in it; run against the pre-fix file it reports exactly this one.

The new test lifts the real function out of the model and runs it against both worlds — an async cache (the server, where this broke) and a synchronous one (the client, where it always worked) — which must give the same answer: a value whose field exists on the destination board by name and type is re-keyed to that board’s definition, and one whose field does not is left pointing at the definition it had, which gains the destination board so the value still resolves.

Cards moved or copied between boards on an affected release are not repaired by this. The entry kept its value but lost the only reference to which field it belonged to, so there is nothing left to match it back with, and those values have to be set again.