Wekan logo

Wekan

Open source kanban board application built with Meteor

Alternative to: trello


About Versions (218)

v10.43

2026-07-27

v10.43 2026-07-28 WeKan ® release

This release adds the following new features:

A Docker Compose file for every FerretDB v1 backend, generated from one source. Thanks to xet7.

docker-compose.yml runs FerretDB v1 on its embedded SQLite. The same FerretDB can store into PostgreSQL, MySQL, MariaDB or SAP HANA instead, and there was no compose file for any of them — those backends were reachable only by editing the default file by hand. There is one per backend now: docker-compose-ferretdb-v1-postgresql.yml, -mysql.yml, -mariadb.yml and -sap-hana.yml.

They differ ONLY in the database. The same WeKan image, the same ~700 lines of environment and the same comments explaining them — they are generated from docker-compose.yml rather than copied, and tests/dockerComposeBackends.test.cjs compares each file’s WeKan service against the default one line for line. A copy drifts on the first setting somebody adds to one file, and then a user following the PostgreSQL file gets a differently configured WeKan than the SQLite file gives them.

Each names its own database service, runs FerretDB with the matching --handler, and says at the top how to start, follow and stop THAT file with docker compose -f. The images are the newest published — postgres:18, mysql:9, mariadb:12 and SAP’s saplabs/hanaexpress — with the LTS alternative named in a comment where there is one. PostgreSQL is confirmed working with Meteor 3; MySQL, MariaDB and SAP HANA say in their own headers that they are experimental, because they are. SAP HANA also needed the wekan/FerretDB release binaries to be built with the ferretdb_hana build tag — without it --handler=hana is an unknown handler — so that build now passes the tag.

build.sh and build.bat offer every docker-compose*.yml in the repo now, not four of the eight, and the test fails if a file exists that neither menu can start, or if a menu offers a file that does not exist.

and fixes the following bugs:

Admin Panel reports show the whole instance, not the admin's own boards. Thanks to xet7.

Boards Report was empty while the Cards report beside it listed cards from thousands of boards. It published userBoardIds(this.userId) — the boards the ADMIN is personally a member of — and on an instance whose admin is not a board member that is nothing at all. Until the pagination fix below, the pane hid it: it rendered every board in minimongo, which the All Boards page had already put there. The Files report was scoped the same way, to the cards that admin can access.

Both cover the whole instance now, as Cards, Broken cards, Rules, Impersonation and Recovery already did. That membership selector was also what kept those two publications honest, so both gained an isAdmin guard at the top — boardsReport asked only for a logged-in user before — and each count method counts the same set its publication pages. Files and Rules name their page now too: opening one card puts its attachments in minimongo, opening a rules editor puts that board’s rules there, and neither belongs in a report page. The Files page is sorted by name, because paging with no sort is paging over natural order — where a document can appear on two pages or on none — and that sort has an index.

The admin lists paginate honestly: one page of ten rows, index-backed. Thanks to xet7.

Admin Panel / People showed the admin on every one of its 578 pages, and Admin Panel / Problems / Broken cards was one endless page under a pager that said “1 / 1”. The same bug in two shapes: the pane subscribes to a publication that sends exactly one page, then reads that page back out of minimongo — which holds far more than the page. The logged-in user’s own record is always there, because accounts publishes it, and so is every card of every board the admin has opened; a plain find(query) cannot tell those apart from the page. So the server names the page now: getPeoplePageIds returns the ids of the People page with the publication’s own selector, sort and window, and the Broken cards, Cards and Boards report publications send the ids of the page they just sent alongside it. Each pane renders that list, in that order, and nothing else.

Every paginated page in WeKan loads ten rows at a time, from one constant, including the pages that draw a pager of their own — the reports and the event streams, the four People panes, Translation, the search pages and the archive. Table.md records the rule and its two deliberate exceptions.

And the counting behind those pagers is index-backed. Every count already asked the database for a count rather than fetching the rows, but several counted and paged on unindexed fields: People sorts users newest-first, so on an instance with 14000 users every page of ten sorted all 14000 first, and Broken cards asks an $or over boardId/swimlaneId/listId/type, which uses no index at all unless every branch has one. The missing indexes are created at startup by the idempotent ensureIndex, so an upgrade adds them by itself. tests/adminPageRows.test.cjs and tests/paginationIndexes.test.cjs pin all of it.

and has the following release-tooling changes:

The release workflow says what worked, what failed, and why. Thanks to xet7.

release-all.yml could fail in ways that left no explanation, or no failure at all: a version bump that committed nothing, a release published with empty notes, a bundle zip that exists but holds no bundle/main.js, an upload that reports success and leaves no asset behind, and a multi-arch image whose manifest is missing a CPU — which nobody notices until a user on that CPU is told there is no matching manifest. Each of those is checked now, and each check says the same three things when it fails: what was expected, what was actually there, and what to do about it. On success it prints one OK: ... line naming what it verified, so the log shows what worked and not only what did not. Every job also ends with a “Job result” step that runs whatever happened, so the run summary is one line per job — OK, FAILED or CANCELLED — instead of eighteen logs to open.

A missing or broken release secret is one named line in the log. Thanks to xet7.

Only three jobs said anything about their secrets. Everywhere else a missing one surfaced deep inside a tool — a login that was refused, a checkout that 404’d — with nothing naming the secret, and the docker job did it only after the multi-arch build had already spent half an hour on an image it could not push. Every job that needs a secret now checks it in its FIRST step and names what is missing. Where the answer is cheap it also checks that the secret WORKS, which is the other half of the question: WEKAN_REPO_TOKEN is asked whether it can push to the repository it is for, the three registry credentials are decoded and logged in with, and the base64 ones must decode. What only the far end can answer is answered where it is used — the Snap Store uploads name SNAP_AUTH when they are refused, and a Launchpad build that fails with an unauthorized in its log says LP_CREDENTIALS may be why.

Every disabled release job runs again, so its output is visible. Thanks to xet7.

snap-qemu (ppc64el, s390x), snap-launchpad (riscv64), snap-variants, ucs and nextcloud were hard-disabled with if: ${{ false }}, so a release never said anything about them at all. They all carry continue-on-error: true, so running them cannot fail a release or hold up another job — the point of running them is to SEE what they report. The three that skip their work while their secrets are unset — snap-variants, ucs, nextcloud — do it with a ::warning:: now instead of a quiet ::notice::, so the missing secrets show in the run summary rather than only in a log.

and has the following developer-facing change:

A test pins that a per-tenant admin sees no report data. Thanks to xet7.

Admin Panel / Problems is instance-wide, so every report publication and count method asks for the SITE admin flag user.isAdmin — not canOpenAdminPanel, which a per-tenant Global Admin passes — and problems is not one of a tenant admin’s tabs. That is what keeps the Boards and Files reports honest now that they cover the whole instance instead of the admin’s own boards, so it is pinned rather than left to be re-derived. The one admin list a tenant admin does get, People, is tenant-scoped in all three places that decide what it shows: the publication, the page-ids method and the count all go through peopleScopeSelector, so its pager cannot count rows its page may not show.

and improves the documentation:

docs/Databases is one directory per database, each with a README. Thanks to xet7.

It was a flat list in which the database was a filename prefix, in three spellings — MongoDB-Driver-System.md, mongodb-avx-qemu.md, MongoDB_OpLog_Enablement.md — plus FerretDB2-PostgreSQL.md and a ToroDB-PostgreSQL directory. Somebody asking “how do I run WeKan on PostgreSQL” had to read the prefixes to work out which files were even about their database.

Now there is a directory each for Migrations, MongoDB, FerretDB — with 1 and 2 inside it, because v1 and v2 are different products with different backends — and ToroDB, with PostgreSQL inside it. Each file keeps its name minus the redundant prefix, and the old PostgreSQL.md, which was an index of FerretDB and ToroDB rather than a document about PostgreSQL, becomes the directory’s README.md.

Every directory has a README.md saying what is in it and where to go next — including FerretDB/1, which had no documentation at all although it is WeKan’s default database and had just gained four more backends. A link to a directory points at the DIRECTORY, not at its README.md, because that file is what is opened by default anyway. Every link into the old paths is updated: in the docs, in docker-compose-ferretdb-v2-postgresql.yml and on the wekan.fi website, along with the relative links inside the moved files, which gained a directory level. tests/docsDatabases.test.cjs pins the layout, the READMEs, the directory links, that every relative link resolves, and that nothing outside CHANGELOG.md still names an old path.

Which other databases run on many CPUs, and what a FerretDB v1 backend would cost. Thanks to xet7.

WeKan runs on every CPU Node.js runs on, and this fork of FerretDB v1 exists because MongoDB publishes no server for most of them. Alternatives.md in docs/Databases/FerretDB/1 answers the two questions that keep coming back: which databases even have images for those CPUs, and what would be missing in FerretDB v1 before it could store into one.

The architecture table is read from each registry’s own manifest rather than from documentation, and it says plainly what that data shows: PostgreSQL is the only widely-portable database server — the only one publishing ppc64le, s390x and riscv64. MariaDB covers ppc64le and s390x, MySQL neither, and MongoDB itself and upstream FerretDB 2 are amd64 + arm64 only. This fork’s own image covers nine platforms.

What a new backend needs is taken from the code: a pure-Go driver, because the binaries are built CGO_ENABLED=0 and that is what makes one build serve nine architectures — which is why Oracle and IBM Db2 are out despite Db2’s ppc64le and s390x images; the three internal/backends interfaces, none of them stubbable; a metadata registry; the SQL features the translation actually uses, including the record-id column that capped collections — and therefore the OpLog — are built on; correct MongoDB semantics on top of that; and a live integration run, which is the whole difference between the confirmed and the experimental rows. Plus the shortcut: a database that speaks the PostgreSQL or MySQL wire protocol needs no new backend, only an existing one that survives its dialect — CockroachDB brings s390x that way, for the price of a test run.

A follow-up groups them, so the reader is not left to find the pattern: they are not fifteen questions but seven families — PostgreSQL-wire, MySQL-wire, enterprise SQL, embedded, columnar, key-value, and what already speaks MongoDB — and inside a family the answer is one answer. The page opens with all of it in five sentences and one table, because the conclusion (verify the three backends that already exist rather than write a fourth) was previously visible only to whoever read to the end.

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