Wekan logo

Wekan

Open source kanban board application built with Meteor

Alternative to: trello


About Versions (218)

v10.40

2026-07-26

v10.40 2026-07-27 WeKan ® release

This release fixes the following bugs:

The right sidebar starts below the header, in both modes. Thanks to xet7.

Its upper part — the panel title, the tabs, the members row — was underneath the two header bars, so the sidebar appeared to start in the middle of itself. On a phone it is position: fixed, and it has to be: the board behind it is wider than the screen, so an absolutely-positioned sidebar pins to the far right of the BOARD and shows as a narrow strip. But fixed means against the viewport, and it was pinned at top: 0 — where the header bars are. There is no number to put there: the header is the quick-access bar plus a board bar whose buttons wrap to one, two or three rows depending on language and window width. So the header measures itself into --wekan-header-height and the sidebar starts at that, kept current by a ResizeObserver — the buttons re-wrapping does not fire a window resize.

A subscription with no board id took the server down; it is answered, not fatal. Thanks to xet7.

publishComposite('board') starts with check(boardId, String). A subscription arrives with a null board id, check() throws, and a throw inside an ASYNC publisher escapes as an unhandled promise rejection — which this app treats as fatal: SyncedCron: Fatal error encountered (unhandledRejection) and Exited with code: 1. One bad subscription stopped the server for everyone, and subscription arguments come from the client, so any client could send it; the app was sending it itself, from a popup that read Session.get('currentBoard') on a page that has no current board. The publisher TESTS its arguments now instead of checking them — a subscription that names no board publishes nothing and readies — and the same guard is on boardCardsWindow and boardCardsLoadingMode. The client no longer sends a subscription for a board id it does not have. Each guarded publisher still marks its arguments with check(x, Match.Any) (b3537985e), because this app runs with audit-argument-checks and Match.test alone is not checking — without that, every subscription failed with “Did not check() all arguments”. tests/publicationArgumentGuard.test.cjs replays the guard and fails on an unguarded board subscription.

The board bar's icons fit two rows, and a desktop-mode board is a desktop board. Thanks to xet7.

The board bar’s icons still took four rows - title, seven, three, hamburger - because the rule that makes each button a flex item of the bar was in boardHeader.css, and the display: flex for those groups in header.css has the same specificity and loads later; it is written in the file that wins now. And lists still stacked in desktop mode: boardBody.css lays the whole canvas out for a phone by WIDTH — display: block on the swimlane, overflow-x: hidden on the wrapper — which makes every list a full-width row and drops the add-list form under the last list. That is mobile mode’s layout now; desktop mode gets a swimlane that is a flex ROW scrolling sideways, so the lists run left to right and the add-list form is the last item of the row — the right in LTR and the left in RTL, by writing direction rather than a hard-coded side.

Mobile mode is the phone layout; desktop mode is the desktop layout, on a phone too. Thanks to xet7.

Five things, one theme: what belongs to the MODE the user chose and what belongs to the width of the window. The board bar’s buttons still took three rows in desktop mode — the metrics were written in boardHeader.css, and Meteor loads components/boards/ before components/main/, so header.css’s margin: 0 6px won at equal specificity; they are written in the file that wins now, and eleven icons take two rows in both modes. The sidebar hamburger is the last button at the right of the last row instead of pinned beside the title. The avatar was cut in half in mobile mode only: the drag-handle toggle in the top bar is also a .board-header-btn, so mobile mode gave it 42px of padding for one icon — the width the avatar needed. Lists stacked one per row in desktop mode, because that layout is chosen by width; it is body.mobile-mode now. And the minicard’s full-height thumb handle, chosen by pointer: coarse, is mobile mode’s too — desktop mode keeps the compact handle in the corner under the menu button.

The board bar's icons fit two rows on a phone. Thanks to xet7.

Eleven icons took three rows under the title: each button was ~20px of icon inside 10px of icon margin inside 6px of button margin — 52px of a 375px bar for 20px of icon — so five fitted a row. A 44px touch target with 2px between buttons is 48px each, so seven fit a row and the icons take two, the first of them sharing the title’s row. 44px is the minimum comfortable touch target, so this is as tight as it goes: one row would need 34px each, smaller than a fingertip. tests/narrowWindowLayout.test.cjs does the arithmetic and fails if the metrics stop fitting eleven icons in two rows.

The avatar lines up with the bell, and "My Boards" is back at the left. Thanks to xet7.

The avatar sat above the bell and against the right edge: its wrapper carries top: -5px from the desktop bar, and #header-user-bar carries 10px of padding on each side — 20px of a 375px bar spent on nothing. Neither applies on a phone now, and the chips behind the mode-toggle icons are trimmed to match. “My Boards” had moved to the middle of the second header bar, which was a scoping mistake: the phone rule that makes the All Boards page a flex column was written for a bare .wrapper, and header.jade puts wrapper on #header-main-bar on every page that is not a board — so the bar became a flex column and centred its items. It is scoped to #content .wrapper now.

The board bar reads left to right, and the header says which mode you are in. Thanks to xet7.

Four things on a phone, all the same in mobile mode and desktop mode. The board’s buttons started on a row of their own under the title, because every group is one flex item and moves as a block; on a phone the groups are display: contents, so each button is a flex item of the bar and they start right of the title. The sidebar hamburger sat on a row below them although two rules were meant to pin it to the top right corner — they were scoped to body.board-view, a class written in 67 CSS rules and set by NO code, so they had never applied; the width-based copy asks :has(.board-header-sidebar-toggle) instead. The mobile/desktop toggle did not say which mode was on: #000 against #666 at 14px is no difference, so the current side is a filled chip in the active theme with a white glyph and the other is faded. And the avatar sat higher than the bell — a bottom margin lifts an item by half of it in a centred row — while its initials sat low in the circle, at y="11" of a 15-unit viewBox with a font-size taller than the box.

All Boards on a phone: the board list scrolls to its last board. Thanks to mimZD and xet7.

Reported against 10.10, again against 10.37, and again against 10.38 after two fixes that made the numbers more accurate instead of removing them. An inner scroller only works when every ancestor between it and the viewport has a definite height, and this chain was built out of viewport arithmetic: the wrapper was 100dvh although it begins below the two header bars, so its bottom sat about two bars below the screen; the layout between had no height at all, so height: 100% under it resolved to auto; and the list was `calc(100dvh

  • 120px)— a guess at everything above it, which on a phone is ~226px, so the list box reached ~100px below the screen and its last rows were under the fold where no gesture could bring them. There is no viewport arithmetic belowbodynow: the page is 100dvh and everything under it is a flex chain, so the list ends exactly where the screen does, whatever the bars above it are. Both modes use the same mechanism.tests/boardListScrollChain.test.cjs` fails if “viewport minus a guess” comes back. Not verified on a device: there is no phone here, and Playwright’s fixed viewport has no browser toolbar.

and has the following developer-facing changes:

build.sh and build.bat always build WeKan before running the tests. Thanks to xet7.

Both built the bundle only when .build/bundle was MISSING — exactly the case where a bundle exists and is stale. The :3000 test server runs that precompiled bundle, so Node E2E and every Playwright browser were testing whatever was built last time, and the run passed or failed on code that is no longer in the working tree. Both delete .build and build every time now, and stop with an error if the bundle is missing afterwards. build.bat was also two menu entries behind build.sh — the dev server’s custom port + ROOT_URL host, and “Install Playwright browsers” — and both are added, renumbered and dispatched. tests/buildScriptParity.test.cjs fails if either script stops rebuilding, if a menu entry has no counterpart, if a Docker compose file one can start and the other cannot, or if a .bat menu prints a number it does not dispatch.

and fills the new strings in every language:

"Open many cards at once" is translated into every language. Thanks to xet7.

The two strings the “Open many cards at once” setting added were the English source in every language, which is what a pull leaves behind for a string that is untranslated everywhere. They are translated directly — written here from each language’s own existing translations, with no external service — using the word for “card” that each file already uses, so they read like the rest of the file. 142 languages; English and its eleven en-* variants keep the source by design. Only placeholders were touched, so no human translation could be overwritten, and filled strings stay local: they are never pushed to Transifex.

Thanks to above GitHub users for their contributions and translators for their translations.