This release adds 1 notable feature for engineering teams evaluating rollout.
✓ No known CVEs patched in this version
Topics
+2 more
Summary
AI summaryFixed sidebar positioning, prevented server crash from null board ID subscriptions, improved UI layout on mobile/desktop modes.
Full changelog
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.
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 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.
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.
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 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.
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.
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.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About wekan
The Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . New English strings for new features at imports/i18n/data/en.i18n.json . Non-English translations at https://app.transifex.com/wekan/wekan only.
Beta — feedback welcome: [email protected]