Wekan logo

Wekan

Open source kanban board application built with Meteor

Alternative to: trello


About Versions (100)

v9.71

2026-06-23

v9.71 2026-06-23 WeKan ® release

This release adds the following features:

  • Sort, search and paginate the All Boards page, #5799: the All Boards page only offered the per-user manual drag order with no obvious way to sort by name. Added a Sort button (Custom drag order / Title A→Z / Title Z→A, stored per user), a board-name search box that spans every category (Starred, Templates, Remaining and all (sub)workspaces), and pagination of the board icons — in the sorted modes the current page is computed on the server (getAllBoardsPage) so only that page is rendered and the page scales to thousands of boards. The search box and pagination controls are placed like Admin Panel > People. Custom (manual drag order) stays unpaginated so drag-reordering keeps working. All server-side filtering/sorting resolves against the effective current user, so it also works when a GlobalAdmin impersonates a user; added Playwright impersonation tests (tests/playwright/specs/38-impersonation.e2e.js). Note: “Stop impersonating” is still a follow-up — impersonation is connection-scoped and currently reverts on a full page reload. Fixed in f74ccbc8f and c1da7a4a3.
  • Fixed (same) width for all lists, #5729: the Set width popup now has a “Same width for all lists” toggle. When enabled, every list on the board renders at one shared width for the current viewer, and dragging the resize handle of ANY list updates that single value so ALL lists change together. The setting is per-viewer/per-board and works for both logged-in users (stored in profile.fixedListWidthBoards / profile.fixedListWidths) and anonymous public-board users (stored in localStorage keys wekan-fixed-list-width-enabled / wekan-fixed-list-width). Enabling fixed width turns off auto-width (the two modes are mutually exclusive); widths below 270px are rejected.

and fixes the following bugs:

  • Due date does not work when the language uses non-Latin (e.g. Persian/Farsi) digits, #5752: in locales such as Persian/Farsi or Arabic, dates and times can be rendered with non-Latin digits (Persian/Extended Arabic-Indic ۰۱۲۳۴۵۶۷۸۹, U+06F0–U+06F9, or Arabic-Indic ٠١٢٣٤٥٦٧٨٩, U+0660–U+0669). The native JavaScript Date constructor only understands ASCII digits, so any such string fails to parse and produces an Invalid Date when setting/updating or comparing dates. Added a normalizeDigits() helper in imports/lib/dateUtils.js that converts those non-Latin digits to ASCII, applied it at every date-string parse boundary in dateUtils.js (via an internal toDate() wrapper) and in the date/time picker submit/change handlers in client/lib/datepicker.js. The normal ASCII path is unchanged.
  • Impossible to create a Link to a whole BOARD if that board already has cards, #5715: in the link-card popup, choosing a board used to auto-populate the List/Card sub-selects, with no way to clear them, so once the chosen board had cards you could only link to a card and no longer to the whole board. The Swimlane/List/Card selects now default to a blank “(none)” option and are no longer auto-selected when a board is chosen, and the popup’s confirm (“link”) button now falls back to creating a board-level link when a board is selected but the Card field is left blank (matching the previously-only-empty-board behaviour). Added tests/linkCardPopup.test.js covering the positive and negative cases.
  • Internal Server Error (500) when attempting to reset a password, #5706: on the Forgot Password page, clicking “Email reset link” could return a raw HTTP 500 instead of sending the reset email / showing success. This happens when the server’s SMTP is not configured (no MAIL_URL / MAIL_FROM, or a bad mail server): Meteor’s Email.sendAsync throws, and the exception propagated unhandled out of the forgotPassword method as an opaque 500. The reset-password / verify-email / enroll-account email-template builders are now hardened so they never throw (guarded user name/language lookups and a safe fallback if translation fails), and Accounts.sendResetPasswordEmail is wrapped so a send failure surfaces as a clean Meteor.Error('email-fail', ...) instead of a 500. Note: this makes the failure graceful, but to actually receive reset emails you must still configure SMTP (Admin Panel mail server, or MAIL_URL and MAIL_FROM). Logic extracted to server/lib/resetPasswordEmail.js and unit-tested in tests/unit/resetPasswordEmail.test.js.
  • Changing the UI language had no effect for some languages (e.g. Chinese zh-CN, zh-Hans/zh-Hant, Arabic ar-*) while others (de, fr) worked, #5756: the custom tap:i18n reimplementation registered each language’s resource bundle under the raw Wekan tag, but i18next (with cleanCode: true) resolves and looks translations up under a normalised code. For region/script-tagged and legacy underscore tags (af_ZA, en_AU, …) the stored code and the lookup code disagreed, so the bundle was never found and the UI silently fell back to English. The loader now normalises every i18next call (supportedLngs, addResourceBundle, changeLanguage, t) through the same code — underscores converted to hyphens and run through i18next’s formatLanguageCode — so storage and lookup always agree for ALL supported languages. Dynamic JSON imports that resolve to an ES-module namespace are now unwrapped safely (without mistaking the data’s own "default" translation key for the module’s default export), and the language-switch handlers in the user header and login form now surface a failed load instead of leaving the UI silently in English. Added positive and negative regression tests in imports/i18n/i18n.test.js.
  • Card labels took two lines / double height on minicards, wasting vertical space, #6424: each label’s name is rendered inside a .viewer, whose global min-height: 2.5vh (intended for the full content editor) forced every minicard label to roughly double height. The minicard text labels now reset that min-height, render the viewer/paragraph inline and stay on a single compact line. Fixed in afbabccd2.
  • REST API: moving a card to another list (PUT …/cards/:cardId with listId) returned HTTP 500 — “fieldNames.includes is not a function”, #6423: the list-move path called the cardMove() activity helper with { fieldName: 'listId' } (a plain object) as its fieldNames argument, but cardMove() does fieldNames.includes('boardId'), so the object’s missing .includes threw and the endpoint returned HTTP 500. The changed field names are now passed as an array (['listId']), matching the cross-board move path. Fixed in 69c899f1f.
  • Notification emails were sent in English despite the user’s language setting, #5875: on the server only the default (English) translation bundle is loaded at startup, so translating a notification to the user’s language fell back to English because i18next never had that language’s bundle. Added TAPi18n.ensureLanguageLoaded() (loads the bundle on demand) and await it before translating in the email notification, EmailLocalization.sendEmail and outgoing webhook paths. Fixed in 22f996903.