Binaries in these bundles
Each bundle carries a Node.js, a FerretDB and the MongoDB Database Tools.
Which source has a given CPU varies from release to release - nodejs.org
builds some architectures, unofficial-builds others, and the
wekan/node-patches build the ones
neither of them does - and not every source publishes a checksum. This is
what went into this release, and which downloads were checked against a
published SHA256.
| Bundle | Binary | From | Version | Checked | SHA256 |
|---|---|---|---|---|---|
| amd64 | FerretDB | wekan/FerretDB | v1.53.0 | verified | eae1f0a8f73bfc97…
|
| amd64 | Node.js | nodejs.org | v24.19.0 | verified | 14b342e71204f811…
|
| arm64 | FerretDB | wekan/FerretDB | v1.53.0 | verified | bdc50caee3ac2849…
|
| arm64 | Node.js | nodejs.org | v24.19.0 | verified | 01443c1e1a29e531…
|
A row saying no checksum published is not a failed check - it is a
source that publishes nothing to check against. Those are the ones worth
fixing at the source.
v10.92 2026-08-15 WeKan ® release
In short: this was tagged v10.92 and never published: its release job
died before running a line of its own script, with Argument list too long,
because the release notes had outgrown the size a single environment variable
may hold — so the release, and every job that needs it, never happened. The
amd64 and arm64 bundles built and verified; only the publishing failed. The
notes now travel from file to file and never become a shell value, which is the
one shape that neither runs a backtick as a command nor has a size limit at all.
Everything below was written for v10.92 and ships here instead. It begins with
what the published Docker image SHIPS, cleaned up against a container scan of
ghcr.io/wekan/wekan:v10.91. That scan reported 80 findings against "Node.js",
three of them CRITICAL, and not one of them was in code WeKan runs: npm and
node-gyp are build tools that were left in the finished image, and their
trees are where tar 6.2.1, sigstore, ip-address and the rest came from.
Both go now, after the install that needs them - 83 of the 120 packages in
programs/server, and npm itself. The rest of that list is the npm packages
Meteor's own packages bundle, which no package.json here can reach:
nodemailer, openpgp, svgo, postcss, nanoid, lodash,
qs, body-parser, cookie, on-headers, tmp, diff,
@babel/runtime and underscore are raised inside the built bundle, to
fixed versions in the same major, by a manifest the release jobs and the
Dockerfile share. Below that: the guard suites that keep both from coming
back, and what could NOT be fixed and why. Then card export: the PDF and
Excel exports of a card were two different answers to "what is on this card",
and are now one - the same fields under the same translated labels, dates in the
reader's own time zone and in the date format the opened card shows, and
a description's markdown drawn as bold and italic rather than stripped. On
top of that, #1173 after eight years: a board, a swimlane or a
list exports to PDF and Excel in that same card layout, from one selection
popup that says what to include. And titles are edited where they are
written: a card's title on the board (#4990, asked in 2022), a board's by
clicking its name in the header bar instead of a pencil beside it. Above all
of it, though: five CRITICAL REST API fixes reported by ybsun0215, the worst
of which let any user with write access to one board destroy the comments,
checklists and history of every card on every board in the instance. Below that:
twelve bug fixes - among them Custom Fields, which the browser tests caught
being unreachable on a card that had none, which is exactly where it is needed,
and a field made from a card that was silently never created - a test that pins
that a browser downloads one language file and not all 246 of them, and 81
languages taken past the words on the board into the menus and the login page,
beside the Export row that read as the lowercase key export in every one
of them because that key had never existed. And then the translations turned out
to have a much older problem than any missing string: 8,716 values were
written in the wrong language entirely, which no count had ever reported
because nothing was looking. Korean held Japanese, Georgian Russian,
Hindi Gujarati, Tamil Telugu — and, once a second check asked about the
Latin alphabet inside a language that is not written in it, Greek held
Italian, Thai Vietnamese and Algerian Arabic French. All of it is
translated now, and the scan that found it stays as the guard, reporting zero
for both of its checks across all 246 files. Below that: the search operators a
user TYPES, in the language they read; the one-letter shorthands beside them,
each derived from that language's own word; and the panels a file never had
because they were added after it was last touched. Then dependency updates,
thirty-odd bug fixes, the developer-facing changes, and the rest of the
translation work.
| Platform | Binary | From | Version | SHA256 |
|---|---|---|---|---|
| amd64 | Node.js | nodejs.org | v24.19.0 | 14b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647
|
| amd64 | FerretDB | wekan/FerretDB | v1.53.0 | eae1f0a8f73bfc979738bfff7284d40fd1bc55de2cc56514721fc155c3624f7d
|
| arm64 | Node.js | nodejs.org | v24.19.0 | 01443c1e1a29e531ccad5a46fefa6df490d2189c49f7955904aecdbb0fe86fdc
|
| arm64 | FerretDB | wekan/FerretDB | v1.53.0 | bdc50caee3ac28495b42d2130b94a042a9dd6d3a38f732cac02b648f36c891da
|
| armhf | Node.js | wekan/node-patches | v24.19.0 | b55350f3071b765a98ed66fdc410657ff168a937935057077fd7ab33cb30b9aa
|
| armhf | FerretDB | wekan/FerretDB | v1.49.0 | 144404fb9793dc8e039874812f4e2cb3e6d8b1df0ffdbe50254e7790a342a2f4
|
| armv6 | Node.js | wekan/node-patches | v24.19.0 | 128ded0cda638c1f144eadb23ad249889515df017d298fd49c8faf3db110f0f1
|
| armv6 | FerretDB | wekan/FerretDB | v1.49.0 | 7c27b2c15448709a24eace9b3c951c62fbe33413f7d20d56cb5520f4436efe2d
|
| armv7 | Node.js | wekan/node-patches | v24.19.0 | 8dbe0a9aa8550ad5275c5538ebf868eb2037f0c4d9cccbe319f63b7e5854cd45
|
| armv7 | FerretDB | wekan/FerretDB | v1.49.0 | 144404fb9793dc8e039874812f4e2cb3e6d8b1df0ffdbe50254e7790a342a2f4
|
| i386 | Node.js | wekan/node-patches | v24.19.0 | 3b0b3bbfe27daf583b3a0f432efacc508407a012cdd9e8847250e7c015565bac
|
| i386 | FerretDB | wekan/FerretDB | v1.49.0 | 1f70cb1687411b2a0fa9ac3b5bfc8c4ed9ce25ec2ddfa17e6fd3efb38136a39c
|
| mac-arm64 | Node.js | nodejs.org | v24.19.0 | 3f1cf157479c1480352083105e13faf9d008ede98e7e157746b6df940d197b94
|
| mac-arm64 | FerretDB | wekan/FerretDB | v1.49.0 | 576364db59dfce3ba564b9a3e484496eb57f95d76d2007b9f83241acdbd2f4fa
|
| mac-x64 | Node.js | nodejs.org | v24.19.0 | d35e95230f46f6f0751df497c56622c6735e05d5e1fb1630996a005b9d328fe4
|
| mac-x64 | FerretDB | wekan/FerretDB | v1.49.0 | 37d70cd90aad6d3867b6686507ff1888f1edf6791818c31d014d130e8f39fc14
|
| ppc64le | Node.js | nodejs.org | v24.19.0 | c510c6ce12f07010f771e6edb22a3fe23f4f2e6f40b1ffd4941aed0646a0d8b3
|
| ppc64le | FerretDB | wekan/FerretDB | v1.49.0 | 7c61d4853d5163ad8761449d693fd458ebbb8611a2351c718014876242c5b1fb
|
| riscv64 | Node.js | unofficial-builds.nodejs.org | v24.19.0 | cd1f14af2812148002f58b58a5f9af512a50e3b8e8c148e0db44019dcb68edfd
|
| riscv64 | FerretDB | wekan/FerretDB | v1.49.0 | bd4912da70f5e6c1475ab989668c76b4ab7db4ee06c44357693df64f5e1d0e0b
|
| s390x | Node.js | nodejs.org | v24.19.0 | a4792e65962ffa0af42627aacf1122a60c3c88dbf4e4184f06820d66f9da8ba4
|
| s390x | FerretDB | wekan/FerretDB | v1.49.0 | no checksum published |
| win-arm64 | Node.js | nodejs.org | v24.19.0 | 8502f4a50b458d4cc38ed8f2001556c2cd239d464920f74017926ccb1e1c157f
|
| win-arm64 | FerretDB | wekan/FerretDB | v1.49.0 | 792166623e774b0af2aced31ed3ae39f545ca5268dc4c2b8d1a329228ff52cbc
|
| win64 | Node.js | nodejs.org | v24.19.0 | 57f71ab3652e797d84acddc79c81cc9ff1c6ddb2a1974cdb83f00fee9bff4c73
|
| win64 | FerretDB | wekan/FerretDB | v1.49.0 | f42c50aa84095a9616b00f27a584c66b7bf79e3b109450c62a5f146ba3c85478
|
This release fixes the following CRITICAL SECURITY ISSUES:
The REST API - what an endpoint authorises, and what it then acts on.
PurgeBleed is the severe one of the The order is what makes it destructive. The removal that follows uses a triple key, Any authenticated user with write access to ONE board - their own is enough - The bulk endpoint had always constrained its lookup with HashBleed: The authorisation was never the problem: both endpoints are admin-only and stay The two sibling endpoints in the same file show what was intended: the list GuestBleed is two paths that are Together they are a channel: a member of a private board writes an outsider's id The invariant already existed, documented on the merge endpoint StaleBleed: Reading the board itself was already refused, which bounds this to the id and A dotted path cannot express this: in Mongo, AuthorBleed: six server paths took So a board member could record "victim created this card" and "victim deleted WeKan had already accepted this exact class as a vulnerability and fixed it forPurgeBleed: the single-card DELETE destroyed the contents of any card in the instance. Thanks to ybsun0215 and xet7.
five. DELETE /api/boards/{boardId}/lists/{listId}/cards/{cardId} authorised
the caller on the board in the URL, and then fetched the card by its id ALONE.
cardRemover runs BEFORE the card is
removed - it has to, so the children's before.remove hooks still find their
parent - and it removes strictly by card id: checklists, checklist items,
comments, the activity history and the whole subcard tree. So it erased those
for whatever card the bare lookup returned, which was any card in the instance,
on boards the caller cannot read, irreversibly.
{_id, listId, boardId}, which a
foreign card never matches. The card SHELL therefore survived and the endpoint
answered 200 with the card id - so nothing in the response said that the
contents of somebody else's card had just been destroyed, and the attack is
repeatable for every card id an attacker learns.
could reach every board on the instance. All deployments with WITH_API=true
are affected.
{_id, boardId}; the
single-card path is the sibling that was missed, the same shape as
PassBleed. It uses the constrained
lookup now, so a card outside the authorised board does not resolve, and
cardRemover is never reached.
HashBleed: the admin user endpoints answered with password and session-token hashes. Thanks to ybsun0215 and xet7.
GET /api/users/{userId}
and PUT /api/users/{userId} serialised the whole Meteor user document with no
projection, so every answer carried services.password.bcrypt - an
offline-crackable password hash - and services.resume.loginTokens, the hash of
every live session with the time it began.
that way. The payload was. Walking the ids that GET /api/users returns
harvested the credential material of the whole instance, and a password hash is
an attack that continues offline long after the export.
projects down to _id and username, and the self view runs delete data.services before answering. Nothing in the code, the CHANGELOG or the
documentation ever said the subtree was meant to be exposed. One helper strips
services and sessionData now, and both endpoints answer through it - the PUT
as well, which returned the same unprojected document after every action.
GuestBleed: an outsider named on a card could read a private board's cards. Thanks to ybsun0215 and xet7.
each defensible alone. The members and assignees ARRAYS on card create and
card update were stored exactly as given, with nothing checking that those ids
belong to the card's board. And GET /api/user/cards answered by CARD
membership, without re-checking the caller's access to the card's BOARD.
onto a card, and the outsider's own my cards feed then returns that card's
title, its board, list and swimlane ids, its dates and its co-members - for as
long as the id stays on the card. Their direct read of the board stayed
Forbidden the whole time, which is what made it quiet.
POST .../cards/{cardId}/members/{memberId}, which has refused a non-member with 400
since #5998. It covers the array
shapes now - single create, bulk create and update - and an id that may not be
assigned is dropped rather than the request refused, so a bulk edit does not
fail over one stale id. The listing is filtered by board visibility as well,
because fixing only the write path would leave every card placed before this
release still answering.
StaleBleed: a removed board member kept seeing the board's id and title. Thanks to ybsun0215 and xet7.
GET /api/users/{userId}/boards selected boards with a dotted 'members.userId'
match, which ignores the membership's isActive flag. Removing a board member
does not delete their entry - it sets isActive: false and isAdmin: false and
keeps it - so a removed member's own board listing went on showing that board's
id and title, indefinitely.
the title. The id is the part that matters, because it is what every other
endpoint in the API is addressed by, and a private board's title is often the
thing it is private about.
'members.userId' and
'members.isActive' may be satisfied by DIFFERENT entries of the array.
$elemMatch is what ties them to the same entry, and it is what the rest of
WeKan uses - the single builder introduced by the fix for
GHSA-gwc4-fw7p-gw58,
whose header note reads "A share entry counts only while it is active,
everywhere". This listing predates that consolidation and was never converted.
AuthorBleed: six paths let the caller choose whose name went into the board's history. Thanks to ybsun0215 and xet7.
the actor's identity from the request body's authorId field, checking only
that such a user exists. An existence check is not an authentication check - it
confirms that the name in the envelope belongs to somebody, and says nothing
about who wrote the letter.
this card" on any board they may write to, and the card document itself
recorded the forged userId as its creator. The six are single card create,
bulk card create, the linked-card form, single card delete, bulk card delete and
custom-field create.
card comments in 8.19, and again for the card PUT handler - whose inline note
still reads "use req.userId consistently (it previously read req.body.authorId
here)". These six were missed, which makes it an incomplete fix rather than a
decision. All six read req.userId now, the session the request authenticated
as, which is the only identity the server can vouch for.
and fixes the following SECURITY ISSUES found by container scanning:
The published image - what it carries that it never runs.
Three CRITICAL findings and most of the HIGH ones were three copies of one It runs in every place a bundle is made, not only in the image: the amd64 build, The rest of that scan was A minimum stays inside the major the Meteor package was built against, and that One pass on the amd64 bundle reaches every architecture, because every other Not everything in that report can be fixed here, and it is worth saying which:
npm and node-gyp's tree are not shipped any more, after the install that needed them. Thanks to xet7.
package - tar 6.2.1 twice and tar 7.5.11 once - and none of them is
reachable from boot.js:
node-gyp and @mapbox/node-pre-gyp are dependencies of Meteor's
meteor-dev-bundle, there to COMPILE native modules during the npm install
in programs/server. WeKan compiles nothing at run time: uWebSockets.js,
bcrypt and argon2 all ship prebuilt .node files that node-gyp-build picks
at require time. Their tree is 83 of the 120 packages that install leaves
behind, and it brought tar 6.2.1 (via node-gyp and cacache),
brace-expansion, minimatch and npm's networking stack with it.
npm itself runs exactly once in the image, for that install. The container
starts bash /build/wekan-entrypoint.sh, which never calls it. Shipping it
shipped its own bundled tar 7.5.11, sigstore 4.1.0, @sigstore/verify,
@sigstore/core, ip-address 10.1.0 and brace-expansion 5.0.4 as image
content no code path can reach.
releases/prune-build-only-modules.mjs removes the first, and it is a
REACHABILITY walk rather than a list of 83 names - start from every dependency
of programs/server/package.json except those two, follow each package's own
dependencies, keep the closure - so it cannot go stale the next time Meteor
changes its dev-bundle. The Dockerfile deletes npm and npx in its cleanup
step; node stays, because that is what runs WeKan.
the arm64 container, the three Windows legs, both macOS legs, and
install-node-for-arch.sh for the emulated arches - each one reinstalls
programs/server, so each one has the tree to remove. The pruned bundle was
booted before and after to prove nothing needs what it takes:
identical failure at the database, no missing module.
The npm packages Meteor's own packages bundle are raised inside the built bundle. Thanks to xet7.
nodemailer 8.0.3 and openpgp 5.11.1
(meteor/email),
svgo 2.8.2, postcss 8.5.1 and nanoid 3.3.15 (meteor/minifier-css), qs
6.13.0, cookie 0.4.1, on-headers 1.0.2 and tmp 0.2.3 (meteor/webapp),
lodash 4.17.21 and diff 3.5.0 (ostrio:files), body-parser 1.20.3
(meteor/oauth), @babel/runtime 7.20.7 and underscore 1.13.7.
Npm.depends names an EXACT version, and meteor build copies that version
into
programs/server/npm/node_modules/meteor/<package>/node_modules. Nothing in
this
repository's package.json is consulted for it - not a dependency, not an
overrides entry - so the only place those versions can be raised is the
bundle,
after it has been built. releases/bump-bundle-npm-deps.mjs does that from the
minimums in releases/bundle-npm-security-bumps.json, installing with
--ignore-scripts so no prebuilt native module is rebuilt by a version bump,
and
replacing only copies BELOW the minimum.
rule was learned rather than assumed: uuid 8.3.2 and 9.0.1 are both affected
and the lowest fixed release is 11.1.1, which moved its entry point to
dist/cjs/index.js - the bundle records dist/index.js at build time, and the
server died on boot with Cannot find module .../uuid/dist/index.js. It is in
the manifest's notFixable list with that error, beside lodash.template,
which has no fixed release at all.
bundle is that bundle with programs/server reinstalled. That reinstall is also
why every leg runs it again: meteor-dev-bundle pins underscore 1.13.7
(CVE-2026-27601) and puts it back over the bumped copy - including in the
Dockerfile, which reinstalls from the .zip.
Guard suites for both, and the Debian findings that no upgrade can fix. Thanks to xet7.
tests/imageBuildOnlyModules.test.cjs pins that the pruner is
reachability-driven
and never touches npm/node_modules, that the image prunes AFTER the install
and
before the bundle is moved, that npm and npx are removed and node is not, and
that every leg which installs programs/server prunes afterwards.
tests/bundleNpmSecurityBumps.test.cjs pins the manifest's versions, the
--ignore-scripts install, the below-the-minimum-only replacement, and that
uuid is NOT in minimums and says why.
debian 13.6 target's 176 findings are ALL Fixed in: - - unfixed
upstream, in perl-base, util-linux, ncurses, glibc and the rest of a
base system. No upgrade closes them; only carrying fewer packages helps, which
is what the build-dependency purge and the pebble removal already do.
lodash.template 4.5.0 has no fixed release; it reaches the bundle through
aldeed:simple-schema.
nodemailer's fifth advisory needs 9.0.1, a major the Meteor email package is
not written against; 8.0.11 fixes the other four.
build/<tool> binaries and build/ferretdb are other repositories'
builds - wekan/mongo-tools-patches and wekan/FerretDB - and are fixed there.
and adds the following new features:
The other direction of the same popup. Importing used to mean one thing: a whole The RTL side of that is ONE rule and not two, which is worth saying because it Placement is a FRACTION between the target's sort and the next one's, not a The file is what the export writes, at any scope, and the SAME checkboxes decide Importing WRITES, so unlike the exports beside it, it asks whether you mayImport into a swimlane, a list or a card, beside the one whose menu was used. Thanks to xet7.
new board, from the full-width page at All Boards. A swimlane's menu now imports
a swimlane BELOW that swimlane, a list's menu imports a list after it - which is
to its RIGHT in English and to its LEFT in Arabic - and a card's menu imports a
card below it, into the board that is already open.
looks like a missing branch: the page carries dir, so the board's row of lists
mirrors itself and "after in sort order" is already "the other side". A
direction
branch in the placement would mirror it twice and put the list back where it
started.
renumbering of every sibling - renumbering is what a board with ten thousand
cards cannot afford, and what two clients doing it at once get wrong. Several
imported items spread evenly through that gap, so importing a swimlane of ten
lists does not put nine of them in the same position.
what comes in: a document full of comments imported with Comments unticked
brings the cards and leaves the comments. A .zip is unpacked in the browser
and
its wekan.json handed to the same method, so there is one import path rather
than two. Nothing is merged - everything created is new, because an import that
half-updated a board would be an edit nobody could undo - and a custom field is
matched by NAME, since an id from another board matches nothing here.
change
the board rather than whether you may see it, and a read-only member is not
offered it at all.
All Boards - the overview and the tiles in it.
In All Boards / Starred, a bookmark tile stood 8px taller than the board tile Two of the four tiles in that grid carry The tile now states the height it actually renders at - 114px, with the padding The last piece of #1173: "combine All Boards / + Add Board / Import is now one full-width page that lists Under the source picker are the same what to include checkboxes every export The round trip was half a round trip: a card imported from a swimlane, list or A An attachment lands where its CARD landed - the list and swimlane it is in now, The whole-board import on the new import page takes a Reading an archive in the browser and sending its attachments as base64 over DDP A Where the files END UP is not decided by the import: The upload is capped as it ARRIVES rather than after A Three things about the page that lists the sources. The pop-over that opens it Import from: and Select what to include: were rows of grey ticks that The two questions sit side by side when the window has room for two 280px And Import without mapping members (map later) is a primary button like the A board on the clearpink theme was a pink rectangle floating inside a grey Two lists decided what a tile is coloured with. There is one list now. Each theme paints The other half of a theme is the text on it. A tile writes its title in The Clean Light theme's top bar is Both are dark now, and so is everything beside them: the bar's labels were The dividers between the bar's groups - the seam that separates the page's Hovering an icon lightens it - And with Member Settings / Change color / All Boards on, a tile is the On the Modern theme the popup that asks all this was a single narrow column And on the Dark theme that popup had no title bar at all. That theme hid Picking Largest grew the page heading and the minicards and left the header The preset was a percentage on the root element, and a root percentage only The preset is now published as a NUMBER as well Nothing renders differently until a preset is chosen: the fallback in every one Colour is inherited, and it was set on A colour painted behind the text needs elements to sit on, and neither choice of The wheel and its Unset button are gone from the popup, nothing reads The text colour beside it stays.
All three places render the same picker, so the Custom color wheel was The wheel is offered from that state too, as the flat category's single Picking a clear theme first still gives the two wheels its colour slide There were two export menus. The board popup wrote nineteen formats of its They are one template now, drawing one TABLE of formats. Each entry says what it All five get the board's panel: what to include in one pane and what to Which scope a popup is, is asked in ONE place - and every scope is named there, The card, list, swimlane and board menus each had ONE row, and each named itself Each of the four now has Export and Import, in that order. The rows are Both rows open the same popup - the same panes, the same table of formats, the Import writes, so it is offered only to somebody who may change the board.A starred page and a Template Container are the same height as a board and as "+ Add Board". Thanks to xet7.
beside it and than the grey "+ Add Board" box, and pulled its whole row up with
it.
border: 4px solid #fff - a bookmark
and the Template Container - to say they are not an ordinary board. The tile's
height was a 72px floor with 42px of padding added outside it, which comes to
the same 114px for a plain tile and to 122px for a bordered one, because on a
content-box element a border is added to the height rather than taken out of it.
The heights all said 72px, so nothing in the stylesheet looked wrong.
and the border folded into it (box-sizing: border-box), which is what the
mobile rules in the same file already do for the same reason. Nothing moves
except the two bordered tiles, which lose the 8px they were never meant to have.
The guard compares RENDERED heights now, borders included, instead of comparing
the declared min-height of two tiles that were both content-box - which is how
this went unnoticed while a test watched it.
One import page, for every source, with the same checkboxes the exports use. Thanks to xet7.
all import options to same template". They were fourteen - a link per source in
a pop-over, each going to its own address - and the page each one landed on
never said which other sources existed, so "where do I import a Jira export"
was answered by a menu somewhere else, if you knew it was there.
every source it can read - Trello, Jira, CSV/TSV, Excel, Kanboard, NextCloud
Deck, OpenProject, GitHub, GitLab, Gitea, Forgejo, Asana, Zenkit, and a previous
export of this WeKan, named with the Product name this instance is branded
with rather than with a product the reader has never seen.
offers, from the same list, and on this side they say what comes IN. That works
for every source without teaching five different creators a selection each: the
parts that were not ticked are taken OUT of the parsed document before any
creator sees it, and a creator that never sees a comment cannot import one. A
section that was not ticked is EMPTIED rather than removed, because the creators
read board.comments directly and an undefined array is a crash where an empty
one is "there were none". A source's own name for a part is pruned with it -
Trello calls its comments actions - and a key this list does not know about is
left alone rather than silently dropped.
/import/:source still works, under its own route name, so every existing link,
bookmark and back button lands exactly where it did.
Imported cards bring their attachments, from a .json and from a .zip. Thanks to xet7.
card menu arrived with its checklists and comments and without its files.
.json export carries each attachment's bytes as base64, and the importer now
writes them the way the board import always has - the server-side Meteor-Files
writeAsync, one attachment at a time. A .zip carries them as the files they
are, named attachments/<id>-<name>; the archive is unpacked in the browser and
each file is put back on the metadata row its id names, so the server sees the
same document either way and there is one import path rather than one per
container.
not the ones it was exported from - and one unreadable file is warned about and
skipped rather than losing the rest of the import. A url attachment from an
older export is still fetched through the downloader that validates and pins
every hop, so FollowBleed stays
fixed on this path too.
.json or a .zip
the same way, through the same reader, which is the case the .zip exists for:
a board whose attachments are too large to sit inside one JSON string.
A large .zip is uploaded and streamed instead of unpacked in the browser. Thanks to xet7.
is fine for a card and wrong for a board: 2 GB of attachments become 2.7 GB in
one message, in the browser's memory and then in the server's.
.zip now goes to POST /api/import/zip as the file itself, and nothing is
ever whole in memory on the way in. The request body is streamed to a temp file
as it arrives; unzipper.Open.file reads the archive's central directory, so
entries are opened on demand rather than inflated together - the same approach
the backup restore already takes; and each attachment is piped from the archive
into the attachments collection by addAttachmentFromStream, which writes it to
a temp file and hands Meteor-Files the PATH rather than a Buffer. That helper is
lifted out of the attachment-copy code that already did exactly this, so there
is one way to add an attachment from a stream rather than two.
addFile fires the
collection's onAfterUpload, which validates the file and moves it to the
default storage configured in the Admin Panel, exactly as an ordinary upload
does.
(WEKAN_IMPORT_ZIP_MAX_BYTES,
5 GB by default), so an oversized archive never lands, and the temp file is
removed whatever happens. An entry's name is data and never a path: only the
attachment id before the first dash is read from it, the temp file is named by
WeKan, and the path is built through the same safeEntryPath containment check
the backup restore uses - so an entry called ../../etc/cron.d/x can only ever
be an attachment with a strange name (ZipBleed).
.json still travels as a document over DDP, which is what it is.
The import page asks with the app's own checkboxes, in two columns. Thanks to xet7.
said "Import board (Trello, Jira, WeKan export, CSV, Excel, ...)" - a list of
five of the fourteen sources, in a menu whose only job is to get to the page
that lists all of them - and now says Import board and nothing else.
were never anything else: a fa-check drawn once per row, the same shape
whether that row was chosen or not, so the answer to "which one did I pick" was
a shade of grey. Both are the app's own animated checkbox now
(.materialCheckBox, the one Admin Panel / Announcement uses), which grows its
green tick out of the empty square over 0.2s, so a click is visible as it
happens. The box and its label are spaced apart by the same amount ticked or
unticked - the checked box is a narrower shape shifted left, so the two margins
differ by exactly that shift - and the row itself declares no alignment of its
own, because the tick's rotation is written for the plain flow and an
align-items: center on top of it lands the tick on the first word.
columns and stack when it does not, so the second one is not below the fold on
a page whose first list is fourteen rows long. No source is chosen to begin
with, and choosing one un-chooses the last, because an import reads one file in
one format; every part is ticked to begin with, because an import that silently
left things out would be worse than one that asks.
Import it sits beside. It was the one unstyled button on the page, which
reads as disabled - and it is not a cancel, it is the same import with one
question skipped.
A board tile in All Boards is the board's whole theme, and light themes are readable on it. Thanks to xet7.
tile, while the flat-coloured board beside it filled its tile edge to edge.
boardColors.css painted
.board-list .board-color-<name> a - the LINK inside the tile, which sits
inside the tile's own 24px/18px padding and so can never reach its edges - and
boardsList.css carried a hand-copied list of seventeen flat background
colours on the tile itself, which is what made the flat themes look right. The
five colour SLIDE themes added later were never copied into that list, so their
tiles kept the default grey and only the inset link showed the slide.
clearblue was in the list, flattened to one hex, so it did not show its
slide either, and Clean Dark and Clean Light were in neither list.
.board-list li.board-color-<name>
where it paints its header bar and its Public Boards row, the copy in
boardsList.css is gone, and a tile is the whole tile at every theme.
#f6f6f6, which is right on the fifteen dark themes and invisible on a light
one: Apple Glass Pastel's tile is a pastel wash from #f6f7fb, so its name
and description were white on near-white and only the tile's shape said a board
was there. Both light themes now write their title, description and archive
line in their own dark ink, and darken the card-count pill and the unstarred
star that sit on the same tile - in ONE block, named as the place a third light
theme goes, because a per-theme copy is what caused the first half of this.
Clean Light's top bar is one shade of dark, icons included. Thanks to xet7.
#F1F1F3, and header.css paints every
icon in that bar white by id (#header-quick-access i.fa). The buttons inside a
ul li escaped that through a more specific color: inherit and took the
theme's own text colour; the house and the notification bell are in no
ul li, so they stayed white on near-white - the bell's LABEL was readable
beside a bell that was not there, which is how it was noticed.
rgba(10, 10, 20, 0.5) and its board title full black, so fixing the two icons
alone would have left the bar at three darknesses. There is ONE shade in it now.
The current entry used to be the dark one among pale ones - a difference
that no longer exists - so it is the full-black, heavier one instead. A bell
with something to report keeps its white glyph, because it is on the red circle
then and not on the bar.
own controls from your account - are white too, a background in the first bar
and a border in the second, so on this theme they were not there at all. They
are in ink now, at the divider's own weight rather than the text's: a hairline
as dark as the label beside it stops reading as a seam and becomes a stroke.
The starred group's outline - the box around the caret, the count and the
star - was white for the same reason, so the three sat loose on this theme; it
is the same 0.7 in ink now, which is the dark outline the phone/desktop toggle
beside it already draws for itself and why that one looked right and this one
did not.
#header-quick-access i.fa:hover goes to
#ccc - which on a dark bar reads as "brighter, so you know you are on it" and
on this one made the house fade towards the bar it sits on. On this theme it
goes the other way, to full black, which is the same message read the right way
round for a light bar.
theme's own fill rather than the accent under a flat white veil. The veil made
every tile one shade lighter than the selected row in the left menu beside it,
which reads as two colours rather than as one page. It reads
--theme-accent-fill now, not --theme-accent: the accent is ONE colour - the
solid end of a colour-slide theme - so on clearorange the tiles came out
flat beside a menu row that slid. The fill is the theme's slide where it has
one and its accent where it does not, which is what every other themed control
already reads. The popup's own All Boards button is filled from the same
variable, because the button and the tiles it turns on are one decision and
were two looks.
of swatches. That theme set width: 260px on every popup's content, so any
popup that asks for width by name - Change Color, Export board, Show on Card,
Show on Minicard - was pinned to 260px whatever it had asked for. A theme
decides what a popup looks like; how wide it is belongs to the popup, and the
declaration is gone.
every popup's header outright, and the header is not decoration: it carries the
popup's title, the back arrow into the popup it came from, and the X - so
"Change Color" was an untitled panel that could only be left with Escape or a
click outside. No other theme does that, and Dark no longer does either.
Member Settings / Font / Size moves all of the text, not the part written in rem. Thanks to xet7.
bar, the left menu, the lists, the swimlane header and every popup exactly as
they were - a setting that half-works, which is harder to use than one that does
nothing.
reaches text whose size is written in a RELATIVE unit: rem is measured against
the root, em against its parent. WeKan writes most of its sizes in px, and
px is absolute - no root percentage moves it. So exactly the rem-sized parts
scaled. The base rule made it worse: html, body, input, select, textarea, button re-stated font: 14px …, so the body took the stock size straight back
off the scaled root, and everything inheriting from the body with it.
(--wekan-ui-font-scale), and every px font-size and line-height in the
client stylesheets - 433 declarations across 37 files - is written
calc(14px * var(--wekan-ui-font-scale, 1)). One preset moves the whole
interface. line-height scales with the size on purpose: 21px of type in an
18px line box is the same setting half-applied.
of those declarations is 1, and the variable is REMOVED for Default, so an
instance where nobody touched the setting computes exactly the sizes it always
did. The base font rule is split into font-family and font-size because the
shorthand's size was the one thing the setting had to be able to move. A guard
fails on any bare px font-size or line-height added later, so a new one
cannot quietly opt out of the setting.
The text colour under Member Settings / Font reaches all of the text. Thanks to xet7.
<body> and the form controls, so it
only ever reached text that had no colour of its own - and WeKan gives most of
its text one: the header bar's buttons, the left menu's rows, a minicard's
title, a list header. Choosing green recoloured the page heading and the menu
and left the rest exactly as it was. It is set on every element now.
.fa is left out, because those are Font Awesome GLYPHS rather than text - a
red alert and a green tick mean something by being that colour - and the icons
that are meant to follow their label already say color: inherit, so they
follow this anyway.
Member Settings / Font / Text background color is removed. Thanks to xet7.
them looks good. On the boxes - which is what it did, from <body> - it painted
the whole window: the board canvas and the empty space under the lists, which is
a page background and not a text background. On the elements that actually carry
text it striped every heading, menu row and paragraph with a full-width band. A
setting nobody can make look good is worse than no setting, so it is removed
rather than tuned a third time.
profile.uiTextBgColor, and no stylesheet rule paints it. A profile that
already HAS a colour is cleaned rather than left dormant: setUiColors unsets
the field on every call, whatever it is passed - and it still accepts the
argument, so an older client cannot fail against a newer server. The schema key
stays declared for exactly one reason: a modifier touching a key SimpleSchema
does not know is rejected, which would leave those profiles the only ones that
could not be cleaned.
A custom colour can be chosen in Member Settings and the Admin Panel, not only on a board. Thanks to xet7.
already written for all three - it was just never reachable in two of them. It
appeared only once a named theme had been SELECTED, and a board always has a
colour whose first entry is flat, so Board Settings / Change Color always
showed it; Member Settings / Change Color and Admin Panel / Settings /
Visibility open on Default (no override) with nothing selected, so both
looked as though they had no custom colour at all.
colour - which is what a custom colour means with no theme under it - and
choosing one applies it over the first flat theme. That fallback was already in
the code that saves; it is now written into the picker's own selection as well,
because otherwise the wheel would store a theme the page did not show as chosen
and the next click would read the selection back as none.
needs. One helper answers "which category's custom colours is this picker
offering", so the wheel, the preview and what gets saved cannot disagree.
Every export popup is one popup with one scope, and looks like it. Thanks to xet7.
own under four subheadings, in two panes across the width of the window; the
swimlane, list, card and checklist popups wrote five in one
narrow column with no panes. So "the export popup" looked like two different
features depending on which menu opened it, and a format added to one was
missing from the other.
is - an icon, a label, and either a path to download or a class to click - and
which scopes it belongs to: BOARD_ONLY for the ones that only make sense for a
whole board (the HTML archive, the dependency graph, the CSV columns and the
exports written for Trello, Jira, Kanboard and the rest), and nothing at all for
the ones that work anywhere, because exportUrl already carries the scope - a
swimlane, a list and a card differ from a board by a query parameter, not by a
route. The board popup is now the same one-line include the other four are, with
no markup and no URL builders of its own; the nineteen helpers that built those
URLs are gone with the list that called them.
export it to in the other, side by side when the window has room and stacked
when it does not, pinned to the viewport so the header and its X are always
reachable. The layout is written against the panes' own class rather than
against one popup's name, so it cannot be true of one menu and not another.
because a scope left out would be read as "a whole board" and offered a board's
formats.
Every menu on a board has an Export row and an Import row. Thanks to xet7.
differently: Export card, Export list / Import, Export swimlane / Import,
Export board. Two of them mentioned an import that was a section inside the
popup rather than a thing you could go to, and the card and board menus did not
mention it at all - so importing into a list was discoverable and importing into
a card was not.
named for what they do and not for what they act on: the menu already says
whether it is a card, a list, a swimlane or the board, so Export card inside
the card menu said "card" twice.
same what to include selection - with one difference: the second pane offers
the formats to write out, or the file to read in. The selection means the same
thing in both directions, which is why it is the pane they share.
That question is asked once, in one function, registered as a helper the four
menus use - and asked AGAIN inside the popup, because a row that is merely
hidden is not a permission check.
Search - finding a card by what people call it.
#5006, open since 2023: "A The issue's title says "number/#", and both of those forms work too: The two halves are joined with OR, which is what makes that safe rather thanCards can be searched by their card number. Thanks to mfilser and xet7.
customer has just hinted us that we are not able to search cards by its number
(#)". A card carries a number, the board shows it, people quote it to each other
in meetings and in tickets - and the one thing that could not be done with it
was
find the card again.
number:12 now does, in the global search and everywhere else that parses the
same query. It is an EQUALITY match on a number rather than a regular expression
on text, which is the whole difference between finding card 12 and finding cards
12, 120 and 312. number:abc is refused with the same "expected a number" error
limit: already gives, because a string never equals a numeric field and a
search that quietly finds nothing teaches nobody anything.
#12 and a
bare 12 search for a label called 12 AND the card numbered 12. A board calls a
card "#12" and a label can be called anything, so which of the two somebody
means cannot be read off the text - answering with both is the only reading that
never hides what they were looking for.
destructive: a search that used to find a label called 2024 still finds it,
and the card numbered 2024 is ADDED to the answer instead of replacing it. An
AND would have found nothing at all in almost every case and looked like the
feature working. Only a term that is entirely digits gets the second half, so
#red is the label red exactly as before. And #12 on a board with no label
called 12 no longer reports "label not found" - the card-number half is a real
answer, and a not-found message beside the card it just found contradicts the
screen.
Board, swimlane and list export - printing a board, and what goes in it.
Two halves of the same menu.
The selection now reaches all of it. PDF, Excel, JSON and .zip took the What a format can DROP depends on what it has, and that is the honest part. A It is a full-width panel, and its X is always reachable. It was anchored to And the menu is a menu again. Twenty-odd entries under three subheadings A rule sits above each subheading, so a group's name says where the group before The central lists in A guard in It was nineteen entries in one list, each spelling out its whole family: A subheading names the family once and the entries under it say only what they The parts checkboxes stay at the top, from the shared popup body: the board The board's Export menu had JSON; nothing else did, and there was no .zip at They are the same export in two shapes, not two exports. The document is written Both halves stream. The JSON writer already wrote a document at a time from raw A SCOPED export is the same #1173 "Add Feature: Print Board Both now render a board as the board's own header followed by every card as the The swimlane and list menus offer the same export, which is the board export Rendering a card block needs the whole board in memory, and The card export's popup had a list of sections, and the server had another one, Both now import The same The export selection reaches every format, and the menu opens wide enough to read at once. Thanks to xet7.
parts checkboxes; CSV, TSV, Kanboard and the eleven JSON dialects built their
own
addresses and could not carry them, so ticking "no comments" changed four
downloads out of nineteen. Every board export link is built by the one url
helper now, so ?fields= rides along with all of them.
CSV has no comments to leave out - what it has is columns, so the selection
lands there: unticking People removes five of them, Dates removes eight, Custom
Fields removes the trailing block. One mask filters the header and every row, so
the two cannot drift apart. A Trello, Jira or GitHub export carries a title,
a description, a due date and labels; those three parts are gated and nothing
pretends to gate what is not there.
the button that opens it and clamped into the window using an assumed width -
and the assumption and the stylesheet disagreed by a few percent, so on a narrow
window the panel's trailing edge, with the pop-over's own X on it, ended up past
the edge of the screen: Escape or a click away were the only ways to shut it. It
is pinned to the top of the viewport now, at the 10px gutter every popup keeps,
and is calc(100vw - 20px) wide - the whole window, less that gutter on each
side - so the header, the X and both panes are always on screen.
were a single column you scrolled past. On a window with room it is now TWO
PANES - what to include on the left, what to export to on the right - each
filling its own width with as many columns as fit, so the whole menu is visible
at once. The panes are grid COLUMNS, which is what makes the right-to-left case
free: a mirrored page puts the first column on the right, so the selection lands
on the right and the formats on the left with no second rule to write or forget,
and the divider between them is a border-inline-start for the same reason.
Below 800px they stack, because popup.css already lays every popup out as a
full-screen sheet there, which is what a phone should get. The 1100px width is
mirrored in client/lib/popupOffset.js, which places a popup using its width:
left at the default 380 a wide menu opened near the right edge lands most of the
way off the screen.
it ended - one rule per heading and no stray separators, which a test counts.
A component that others import loads its own template, so the client bundle cannot die at startup. Thanks to xet7.
Template.exportScopeBody.helpers(...) runs at module scope, and it throws when
that template is not defined YET. That does not break one popup - it stops the
module evaluating, so every template registered after it never registers either.
The visible symptom was the SIGN-IN page: a blank form and
Uncaught TypeError: can't access property "helpers", Template.exportScopeBody is
undefined
[UserAccounts] Warning no template passwordInput found!
client/features/*.js import a component's .jade before
its .js, which is enough for a component nobody else imports. The export popup
body is imported by the sidebar, by the card details and by the import page, and
whichever of those is reached first evaluates it - long before the feature list
gets to the template. It imports its own .jade now, so the order is a fact
rather than a hope.
tests/clientBundleImports.test.cjs checks the whole rule, and found
a second component with the same fragility that had not fired yet -
migrationProgress.js, imported by boardBody.js. Fixed the same way.
The board Export menu is grouped under subheadings instead of one flat list. Thanks to xet7.
"Export / CSV (,)", "Export / CSV (;)", "Export / TSV", then eleven lines
beginning "Export / JSON /". The part that differed - the only part worth
reading - started two thirds of the way along every line.
are: PDF, Excel and HTML, then Dependencies (JSON, SVG), CSV
((,), (;), TSV) and JSON (JSON, JSON without attachments, .zip, Kanboard,
Trello, Jira, NextCloud Deck, OpenProject, GitHub, GitLab, Gitea, Forgejo,
Asana, Zenkit). The .zip sits with the JSON it is a container for.
passes hideFormats because it lays the formats out itself, and borrows the
selection rather than growing a second copy of it. Its links are built by the
same url helper every other menu uses, so a menu cannot send a different
selection than the one on the screen. Nothing was dropped in the regrouping, and
a test walks the whole list in order to say so.
JSON and .zip export at every menu, streamed, with the attachments as files. Thanks to xet7.
all.
Both are now offered wherever an export is - the board, a swimlane, a list, a
card and a checklist - from the same popup, with the same checkboxes deciding
what goes in.
by models/exporter.js's streaming writer either way, so a .zip's
wekan.json
and a .json download of the same scope are the same bytes. What differs is
where the attachments are: base64 INSIDE the document for JSON - or omitted,
with
the existing "without attachments" option now offered at every menu - and beside
it under attachments/ as the files they are for the .zip.
cursors with backpressure; the .zip gives it a PassThrough that archiver
compresses as it fills, and pipes every attachment from the file store rather
than reading it into a Buffer. A board with a gigabyte of attachments costs a
gigabyte of disk reads and not a gigabyte of RAM - which is what makes the
.zip
the shape to use when the JSON is too large to hold as one string.
wekan-board-1.0.0 document with fewer rows in it,
plus the lists and swimlanes its cards refer to, so what comes out can be
imported back into somewhere. A section the popup did not tick is an EMPTY array
rather than a missing key, for the same reason. A checklist scope exports the
card that holds it, because a checklist alone has nowhere to land.
A board, a swimlane and a list export to PDF and Excel in the card export's own layout. Thanks to xet7.
with Params" has been open since 2017, and two things were missing. There was no
CHOICE of what to print: the board's Excel and PDF exports took everything they
knew how to render and nothing else, while the card export already had a popup
with a checkbox per section. And they did not LOOK like the card export - the
board's Excel export was a spreadsheet table, one row per card and eighteen
columns, which is a data dump rather than a printed board.
CARD export's block, drawn by the card export's own code - cardBlockLines()
for the PDF, ExporterExcelCard.renderCardBlock() for the Excel one - so a card
looks the same whether it was exported alone or as part of its board, and the
two cannot drift into two layouts again. Each card starts on its own page in the
Excel export, because a printed board is read a card at a time.
with one more parameter saying which cards are in scope. They sit beside "Copy
link" rather than below the permission checks, because exporting is reading. The
file is named for what was exported - a PDF titled with the board that holds one
list is a file nobody can place afterwards.
models/server/ExporterExcel.js STREAMS on purpose - it was rewritten that way
after the in-memory version ate gigabytes on boards with thousands of cards. So
that exporter is still there and still reachable: unticking Card details
asks for it. That is a checkbox in the popup, not a silent fallback nobody can
see.
One selection popup, and one list of what an export can contain. Thanks to xet7.
under a comment reading "Must match ALL_FIELDS in
models/server/ExporterExcelCard.js" - which is a comment, not a mechanism. A
section added on one side and forgotten on the other is either a checkbox that
does nothing or a section nobody can turn off.
models/lib/exportFields.js, and
so do the board, swimlane and list popups, which are ONE shared body -
exportScopeBody - included with a different scope each. The selection is
remembered for the session, because somebody printing a board rarely wants a
different shape for each list of it.
?fields= gates the same sections in both formats: the card export's
checkboxes used to be labelled "fields to include in Excel export" and did
nothing to the PDF, so one popup meant two things. A section a request does not
name is not rendered and, where the export is the only reason to read it, not
even fetched.
Titles - renaming a thing where its name is written.
A list's title has always been edited in place: click the heading, type, save. The minicard's title text is now an The minicard sits inside the link to the card, so a click in the open editor Half of the title edits; the other half drags. With drag handles OFF a card The opened card's title splits the same way, and its drag handle now appears The board's name in the first header bar had a pencil next to it. Two targets The name itself now opens the rename popup - the SAME A board whose title is EMPTY renders no text at all, and an element with noClicking a card's title on the board edits it there. Thanks to bonnebulle and xet7.
A card's title could only be changed by opening the card, so correcting a typo
on a board of forty cards was open, edit, close, forty times - which is what
#4990 asked about, in 2022.
inlinedForm, the same component the list
heading uses, with the same textarea, Save button and X. Only the TEXT opens it,
and only for somebody who may write: the complete checkbox, the linked-card
icons and the card number beside it keep doing what they did, and everything
else on the card still opens the card. A title nobody may edit still opens it
too.
would have navigated away mid-rename. That default is cancelled - except on the
Save button, whose own default IS the submit, and which the browser picks over
the link around it because the innermost element with an activation behaviour is
the one that runs. An empty save is a no-op rather than a way to end up with a
card that has nothing to click.
is dragged by its own body, so a title that is entirely an edit target leaves
nowhere on that line to take hold of - a grab that moves a few pixels is a
click, and the editor opens instead of the card moving. The edit target is the
LEADING half (left in English, right in Arabic, from one logical edge) and the
trailing half is there to drag from. With handles ON the handle is the only drag
source, so nothing has to be reserved and the whole title edits.
only when drag handles are on - with them off, the title bar is what moves the
window. The drag surface there is the header ROW rather than the heading: a
heading is only as wide as its own text, so on a short title "the trailing half"
was a few pixels and the empty space beside it - the obvious place to take hold
of - belonged to nothing. The heading fills the row now, and the handler steps
aside for the buttons in it, for the drag handle when there is one, and for the
half that edits.
A board is renamed by clicking its name, and the pencil beside it is gone. Thanks to xet7.
for one job, and the smaller of the two was the one that did it.
boardChangeTitlePopup
that the pencil opened, with the title and the description in it, so only what
you click to get there changed. It is opened with the board as its data context,
because that bar's context is the page rather than the board. Board admins only;
everybody else sees a plain title, as before.
content is zero pixels wide - so there would be nothing to aim at, and an empty
title could never be given one. The clickable title carries a minimum width and
height for exactly that.
Card and minicard menus - the settings about a thing, in that thing's menu.
What subtasks DO on a board - whether they are shown at all, which board and It is behind a hamburger at the end of the Subtasks heading on an opened The heading IS the control that folds the section, so the hamburger's click has It is the SAME template in both places, with its own state, helpers and The heading row named the columns of a ROW - Show on Card over the checkbox The first heading also said what the popup's own title says. Show on Card opened Both are gone, and the two-pixel rule under them with them - one more line Board Settings / Card Settings is a table of twenty-four settings with two It is now also the first entry of the two menus it is about. The hamburger on an Both are wide, and lay their rows out in columns. Two dozen settings in one The permission is the one Board Settings already used - a board admin - asked It was in the right sidebar under the People and Organizations tabs, which is It is ticked when the text is SHOWN, which is what a board does out of the box. It is the one PERSONAL row of a board-wide table - the user's own profile, or The move found a bug. The setting was written out three times, and the The board's LIST of custom fields - where a field is created, renamed or They are one group now, under Watch and its rule: Custom Fields first, Card Settings is gone from Board Settings. Every setting in that table is Everything about custom fields is on the section that shows them. It was One place now: the hamburger at the end of the card's own Custom Fields It was a line in the card's menu that read "Show list on minicard", or "Hide It shows the name of the LIST the card is in on its minicard, which is worth It belongs to the card rather than to the board, so it is drawn only in the On the minicard itself the name sits on its own line under the badges. It was Two popups of the same menu answered one question. Change Background Image The upload is under the URL field now - a picture from the web and a picture In the same popup, Unset was five blank lines and a rule below the Save The two sections at the top of the right sidebar could not be folded at all. Both have a caret in front of their heading now, and the heading is the button
The choice is the reader's and lasts the session, not the board's: neither A caret leads the group, at its leading edge - left of the lock in a The buttons a board's own Rules put in that bar are NOT folded with them: All three folds share one store, one helper and one class The heading read "Activities" and beside it sat an eye and the words "ShowSubtasks Settings is in the menu of the subtask it is about. Thanks to xet7.
list a new one is deposited in, and how a parent is named on a minicard - was
in Board Settings only: open the sidebar, open the board menu, find Subtasks
Settings. Somebody who wants to change it is usually looking at a subtask on an
opened card at that moment.
card - where the card and the minicard already keep theirs - and clicking it
opens the same popup Board Settings did. Board Settings no longer has the
entry: the settings are where subtasks are.
to stop there. Without that, opening the menu would close the thing it was
opened from.
handlers, so neither place needs code of its own and the two lists cannot
drift. That is what the move needed: a template included in two places cannot
reach its parent's helpers, because a helper is looked up on the template it is
written in.
Show on Card and Show on Minicard do not print their own title again. Thanks to xet7.
and Description over the setting's name - which is what they labelled, but not
what they looked like. The rows flow into as many columns as the window fits, so
the two headings sat above the FIRST of three or four columns and read as if
they named those: Show on Card over one column of settings and Description
over another, when both columns hold the same kind of row.
a popup titled Show on Card and repeated it a line below, and the same for
Show on Minicard.
across a popup that is a list of lines. A row is a checkbox and the name of a
setting, which needs no heading over it. One template serves both popups, so
both lose it together, and the sticky positioning that existed only to keep
those headings in view while the rows scrolled goes with it.
Show on Card and Show on Minicard lead the card's and the minicard's menus. Thanks to xet7.
columns - what a card shows, and what a MINICARD shows - and it lived in one
place only: the right sidebar, four clicks from the card whose fields it
decides.
opened card offers Show on Card, the hamburger on a card on the board offers
Show on Minicard, each followed by a rule like every other group in those
menus. Both open the SAME popup with the other column hidden by a class, so
there is still one list of settings and one set of handlers: a setting added to
the table appears in all three places by itself, and none of them can drift from
the others.
column is a list nobody sees the end of - "is Description on?" was somewhere
below the fold. With one of the two checkbox columns hidden each row is half as
wide as it was, so the popup is min(90vw, 900px) and the rows flow into as
many columns as fit: one on a narrow window, three or four on a wide one, with
the headings still spanning the width because they name the columns of a ROW
rather than of the grid.
in the menu and again where it acts. The one menu is opened from two places and
its data context is the card either way, so the opener says which hamburger it
was in client/lib/cardMenuSource.js: a
module-level reactive value, not a field on the card document, which is gone the
next time Blaze re-renders the popup with a fresh copy of it.
Hide minicard label text became the Labels text row, and now works when logged in. Thanks to xet7.
not where anything else about a minicard is. It is a row of the same table now,
directly under Labels, named "Labels text" with the Labels icon and the
Description Text icon in front of it - because it is about what those labels
look like on a minicard: coloured words, or coloured bars with the words left
out.
The stored flag is a "hidden" one, so reading it straight through would have
left the box unticked for the default behaviour, which reads as a broken
checkbox.
this browser when nobody is logged in - so it appears in the minicard's view
and nowhere else, and it is offered to EVERYBODY. A reader who is not a board
admin gets that row alone rather than a table of checkboxes the server would
refuse.
minicard's own copy only ever wrote localStorage: a logged-in user toggling it
set something nothing reads, because for a user the value is read from the
profile. One module reads and writes it now
(client/lib/minicardLabelText.js), so the
two halves cannot disagree again.
Custom Fields and Edit custom fields are one group in the card's menu. Thanks to xet7.
deleted - was in Board Settings only, and the picker for which of those fields
are on THIS card was down among Voting, Planning Poker and Spent time. Two
halves of one subject, three groups apart.
because a field has to exist before a card can be given it, then Edit custom
fields, then a rule like every other group in that menu. A board admin's, the
same as in Board Settings.
in the menu of the thing it is about now, so a board-wide entry as well would
be a third way to one list - and the way that is furthest from what it changes.
The table itself is unchanged; the two menus include it.
spread over three places: Board Settings had the board's LIST of fields behind a
right-sidebar view, the card menu had an entry for that list and another for the
picker of which fields are on this card, and the picker had a cog that jumped
back to the sidebar - closing the menu and the card pane on the way.
heading. It opens every field the board has, ticked when it is on THIS card,
with a pencil each and, under a rule, Add custom field. Edit and Add open in
the same pop-over on top of that list, so the back arrow returns to it and the
card stays open behind; they are the board's own forms, not second copies of
them. The card menu's entry and the Board Settings row are both gone, and so is
the wrapper popup that nothing opened any more.
Show list on minicard became the List title row of Show on Minicard. Thanks to xet7.
list on minicard" once it was on - a menu entry that changes its own name is
the shape a checkbox has, drawn as a sentence. It is a checkbox now, the FIRST
row of Show on Minicard, above Received, named List title and unchecked by
default, which is what the field itself has always been.
having where a card is read away from the column it lives in: a swimlane,
search results, My Cards. The board-wide "Show lists" row further down the same
table turns that on for EVERY card; this one is for a single card that needs it,
and either is enough for the name to show.
minicard's own view - Board Settings has no card to act on - and only for
somebody who may change that card, which is the permission the menu entry had.
The card is passed into the popup and re-read from the collection when it is
toggled: a minicard's menu is opened from the board, where there is no "current
card" to fall back on.
landing BESIDE them: the badge row is float: inline-start, so the line after
it shared its row and read as one more badge rather than as the line it is.
Change Background Image holds both ways of setting one, and Unset sits beside Save. Thanks to xet7.
asked for a URL; Board backgrounds listed the images uploaded to this board
and carried the upload button. So the popup that reads as the place to set a
background had half of the ways to set one, and the other half was behind a
different entry.
from this machine, read together. Board backgrounds keeps what it is named for:
the images already there, to set active, download or delete. The uploader moved
as its own template, once, rather than being copied.
it belongs beside. They are one row, Save first, sharing the width. Unset is
type="button": inside that form a button with no type is a SUBMIT button, so
beside Save it would have saved the URL it is meant to clear.
Members and Labels fold in the board sidebar, by their headings. Thanks to xet7.
The People / Organizations / Teams / Domains tabs with their avatars, and every
label of the board, were always open and pushed everything under them down the
panel.
got below them. Folding Members hides the tabs and the avatars in them;
folding Labels hides the labels and the + that creates one. Enter and Space
do what a click does.
decides anything on the server, so folding them for yourself must not fold them
for everybody. Activities is the exception and stays as it is - its caret
writes board.showActivities, which also decides what the publication sends.
The board's controls in the header bar fold into one caret. Thanks to xet7.
left-to-right page, right of it in Arabic, because its direction comes from the
same rule every other caret uses. Folded, the seven controls after it are gone
dependencies and Multi-Selection - and the bar is the logo, the board's
name and the caret.
somebody added those to this board on purpose, so the standard controls fold
around them. It is named by the words the app already has - Collapse and
Uncollapse - so no key was added to 147 language files to say them again, and
it answers Enter and Space like the button it says it is.
(client/lib/foldState.js): a caret in the header
and a caret in the sidebar pointing different ways in one language is the bug
that avoids.
The board sidebar's Activities has a caret instead of an eye. Thanks to xet7.
activities" - one setting with two controls and its own name said twice. It is a
caret in front of the heading now, and the heading is the button: click it and
the activities appear under it, click it again and they are gone. That is the
same control every collapsible section of an opened card has, from the same
function (client/lib/sectionCaret.js), so the
caret cannot point one way on a card and another in the sidebar of the same
language - it points down when open, and toward the text when closed, which is
right in English and left in Arabic. It carries role="button" and a tabindex,
so Enter and Space do what a click does.
and fixes the following bugs:
The release workflow - what reaches the Release page.
v10.91's release run failed in four jobs, and three of them - s390x, The step collected the file with two patterns - It is deduplicated now, and the attach is confirmed from the other side the way The fourth failure, armhf, is not this: v10.92's release job failed before running a line of its script, and with it Nothing in the step was wrong. The notes are the whole newest CHANGELOG The environment was itself the fix for the PREVIOUS failure. Interpolated So the notes stop being a shell value at all. The guard test is rewritten around the new shape: no changelog job output, noThree snaps built, published, and then fell off the Release: one file listed twice. Thanks to xet7.
ppc64el and riscv64 - had done all the work: each snap BUILT on
Launchpad and each was published to the Snap Store. What failed was attaching it
to the GitHub Release:
HTTP 404: Not Found (https://uploads.github.com/repos/wekan/wekan/releases/370103352/assets?label=&name=wekan_10.91_s390x.snap)
wekan_${VERSION}_<arch>*.snap
and *_<arch>.snap - and a snap called wekan_10.91_s390x.snap matches BOTH,
so the same path was passed to gh release upload --clobber twice. Asked to
attach one name twice, it deletes the asset it has just uploaded and then 404s
on it. The two other places in the same job that build this list were already
deduplicated; this one was missed.
the native snap job already did it: read the release's assets back and fail if
this snap is not among them. An upload that reports success and leaves nothing
behind is the failure nobody notices until somebody's download 404s - and here
every job that lost an architecture had already said the snap was built and
published.
snapcraft died with
SSLEOFError while downloading the build log from Launchpad, on all three
attempts, and produced no snap. That one is Launchpad's side of the wire.
v10.92 could not start bash: the release notes outgrew an environment variable. Thanks to xet7.
every job that needs the release - snap, docker, AppImage, the Windows and
macOS bundles - so nothing published at all:
##[error]An error occurred trying to start process '/usr/bin/bash' with
working directory '/home/runner/work/wekan/wekan'. Argument list too long
section - prepare measured them at 172,458 characters - and they reached
the step as env: CHANGELOG:. Linux caps a SINGLE argv/envp string at
MAX_ARG_STRLEN, 128 KiB, so execve refused to start the shell. The amd64
and arm64 bundles had already built and verified; only the publishing died.
inline as ${{ }}, the notes become part of the shell SOURCE, so a backtick in
any code span runs as a command: that is how v10.59 published nothing and
printed Incorrect: command not found. One shape is unsafe and the other does
not scale, and this release was the first big enough to find the second wall.
releases/release-notes.sh prints
the CHANGELOG section for a version, reading the file itself and taking its
arguments from the environment inside a QUOTED heredoc, and each job appends its
stdout to release-notes.md. Only file PATHS are passed around, which has no
size limit and leaves the text as data that no shell ever parses.
prepare keeps the validation - a missing section is still cheap to fix there -
but no longer publishes the text as a job output, because an output nobody can
safely consume is a trap for the next person to find. The release job and the
notes-rewrite job check out ref: main, the same ref prepare read.
CHANGELOG in an env:, no interpolation into a script, and the extraction
script is what all three consumers run. It passed throughout this failure,
because it only knew about the injection.
Starting up - what a browser sees while WeKan cannot yet serve.
"We upgraded to 10.91 ... Gateway timeout appears" - WeKan does not open its web port until the database answers, and in a container The entrypoint now asks whether the database answers (one A container waiting for its database says so, instead of timing out. Thanks to Alishara and xet7.
#6595, from the same reporter as
#6592, whose fix shipped in 10.91.
It could not reach them: that fix is the SNAP's, and this is a container.
nothing else was listening while it waited - so a reverse proxy in front
returned a gateway timeout, and that is the same symptom for two completely
different faults: WeKan is broken, or the database has not come up yet.
ping, with the
driver already in the bundle), and while it does not it serves the same bridge
page the recovery case uses, saying WeKan is waiting for its database and
where to look. What is bounded is the PAGE, not the wait: a database can take
minutes to come up after an update, and giving up on it would be worse than
waiting - so when the window ends the page stops, WeKan starts, and WeKan keeps
waiting exactly as it did before. WEKAN_DB_WAIT_PAGE=false turns it off.
Performance - what the database is asked, and what it has to walk.
Reported by email against 10.91: "Still slow on loading cards. Test server with It was. The activities publication even explains that it keeps its selector flat "so Each new index matches a selector the app really makes WITH the sort it reallyCards, activities, comments and checklists are indexed at last. Thanks to xet7.
me as only user." One user is the part that says what it is NOT - with nobody
else on the server there is no contention, no queue and no lock, so the time is
going into the queries themselves.
cards, activities, cardComments, checklists and
checklistItems had no index at all - everything a board draws and everything
an opened card pulls in. "The cards of this list", "the comments of this card",
"the newest activities of this board" each walked the whole collection. That is
invisible on a demo board and expensive on a real one, and worse on FerretDB,
whose SQLite backend has to walk the same documents.
both push down to FerretDB v1 (SQLite)'s index instead of forcing a
full-collection scan" - and there was no index for it to push down to.
uses, so it serves both the filter and the order: a filter-only index still
leaves an in-memory sort of everything it matched, which on a board with a year
of history is the slow half. They are created through the same idempotent
ensureIndex every other index here uses, so a restart does not rebuild them
and a backend that refuses one logs it instead of stopping the server.
Attachments and the snap's databases - what can be read, and what cannot.
Moving from MongoDB CollectionFS to File-System stopped on some attachments A CollectionFS filerecord points at its binary by A binary that really is missing - metadata restored without the chunks - is no An instance whose MongoDB data was written by a MongoDB older than any FerretDB was unreadable BECAUSE it was up: SQLite has one writer, so the second The live one is asked first now - Moving attachments out of CollectionFS no longer deletes a binary two records share. Thanks to sbruckmueller and xet7.
with FileNotFound: file 66336fc372e64200010f4832 was not found, and the
reporter had it exactly: it is the identical files.
copies.<coll>.key, the id of
a file in the cfs_gridfs.<coll> bucket, and TWO filerecords can carry the SAME
key - the same file attached twice, or a board copied with its attachments. The
migration deleted the binary as soon as it had moved the FIRST of them, so the
second read a file that was no longer there. The binary now goes only when no
other filerecord still names it, and when the question cannot be asked the
binary stays: a file left behind can be removed later, an attachment deleted out
from under another record cannot be brought back.
longer a MongoDB stack trace naming a GridFS id. It names the attachment, says
where it was looked for, and counts as SKIPPED rather than failed, because there
was nothing to move. The Admin Panel's summary line shows how many were skipped
and how many failed, so a run that leaves attachments behind cannot look like a
run that moved everything.
A snap whose MongoDB files no reader can open stops comparing them with FerretDB. Thanks to mueschel and xet7.
mongod
the snap carries printed this at every start, before the site came up:
BOTH databases have been written to since the migration and then [autopick] reading both databases to see which one holds the work ....
mongodb_has_data answers "are the files there", not "can anything here open
them", and on that instance those are different answers - mongod 7, 5.0 and
4.2 each refused the files in turn. So the comparison ran on a copy that cannot
be served, and it is not a cheap question: it starts a mongod that cannot open
the data, and then a SECOND FerretDB against the SQLite directory the running
one already holds.
migration-control has already tried every reader in the snap and left
.mongodb-data-too-old behind when none could open the files. With that marker
present the MongoDB copy is no longer a candidate, and WeKan serves the FerretDB
that has the data. Nothing is deleted and the marker stays: it is true, and it
is right again on a snap that can read those files.
Comparing the two databases reads the one that is already running. Thanks to waltermhl and xet7.
snap run wekan.database-compare answered MongoDB: <unreadable> and
FerretDB: <unreadable> on a live instance whose wekan.sqlite was 85 MB and
whose site was up.
FerretDB the comparison started against the same directory did not get the data.
The tool then reported the database it had been talking to all along as
unreadable, and refused to choose.
evidence only counts and sorts - and a
second copy is started only for a database that is not running. Both speak the
MongoDB wire protocol on the same port, so which one is answering is asked
rather than assumed: FerretDB names itself in buildInfo and a mongod does
not. Reading one as the other would be a wrong answer given with confidence,
which is worse than "unreadable".
The page sidebar - the controls of a page that has no sidebar of its own.
Rules, My Cards, Due Cards and Global Search each keep their controls in the Every rule for that class is scoped to They are rows now: one per line, the width of the panel, with the icon in aThe controls in the page sidebar are rows, not three links run together. Thanks to xet7.
shared page sidebar, and each draws them as .board-header-btn - which is what
they were when they lived in that page's own second header bar.
#header-quick-access, so inside the
sidebar they had NO styling at all. On the Rules page that is three bare links
jammed onto one line - "← Board 🔀 Workflow view ⇄ Import / export" - with the
icons run into the words and nothing to click but the text itself.
fixed column so every label starts at the same x, and a hover to click against -
the shape the board sidebar's own rows have. Both sidebars built on that shell
get it, All Boards' as well as the page one.
Minicards - what a click on one does.
A hundred and fifty icons, eight to a row, in a popup 380 pixels wide: a column It is as wide as the colour pickers now - Clicking a label opened the card's labels popup AND the card details behind it: It stops at the label now. A click in the labels AREA that is not on a label isThe stickers popup is wide enough to see the stickers. Thanks to xet7.
taller than the screen, so choosing a sticker meant scrolling past most of them.
min(90vw, 720px) - and the picker
is a grid that fits as many columns as the width allows instead of stopping at
eight. The clamp that places a popup knows the new width too, or a popup opened
near the right edge would have landed half off screen.
A label on a minicard opens the labels, and not the card as well. Thanks to xet7.
the click reached the minicard too, so one click did two things and the one
nobody asked for was underneath the one they did.
still the card's, as before - and which label was clicked is read from the
EVENT rather than from :hover, which answers about the pointer and on a touch
screen can still be true for whatever was tapped last.
Card details - the card as it is opened and edited.
Everything about custom fields moved to the hamburger at the end of the card's So the one way in existed only once you were already through it. A card with no The heading is drawn for every card its reader may write to now, the same The anchor that used to open the old popup went too: the move took away its Two things wrong with the popup the Custom Fields heading opens.
A new field made from it never appeared. Add custom field sits OUTSIDE the Both halves are fixed. The popup hands the form an empty context, because a new The pencil that edits a field sat on a line of its own under the field's name. A pop-over draws its header from its title, and with no title it renders as Ten of them were in that state, and each is titled the way the other 151 They were added to EVERY language file at the same position, as the English The other mechanism, "Upload background image" picked a file and then nothing arrived. Two fields of Both come from one builder now, in One popup answers the whole question now - the URL, the upload, and the Three things around the upload made the failure impossible to read, and are It is total now: initials, then a fullname, then the first letter of a username The dropdown that chooses a card's date format disappeared, and nothing brought Two things were folding it. #1591 The #1591 half is gone - the handler, the caret it drew, the rule that hid the Moving eleven sections with a script is how markup disappears without anything All four now have a test of their own, because each was invisible to everything The Copy card link to clipboard button carried only Grouping the fields gave each group a header LINE of its own, and for a group There is one row of titles now, with the caret at the start of it: Labels The rule moved with it, from inside a field to the group, where it spans the Requested By and Assigned By were set by clicking the word "Add". They Checking where else those two live turned up a round trip that lost them, which The board's Excel TABLE did not export them at all, so that one gained two And from other trackers, where the same idea has another name: Jira's Grouping the fields left two things crooked. Members, Assignee and Creator were Every field in a group keeps its own title again. The group's header names the The caret beside Labels folded the Labels field and left Stickers and The fields belong to FAMILIES, and the families are what fold now:
One caret per group, on its title, at the reading direction's start. The fields Members reads Members, Assignee, Creator, which is the order it was asked Four small things about those separators, all of them visible at a glance and The rule drew a border of its own, darker than the line the card already had Labels had a rule above it. It is the first section: there is nothing above And the rule above Activities was to the LEFT of the heading rather than Two things the collapsible sections got wrong the first time, both visible the Short rules everywhere. Two headings for one section. The Checklists and Subtasks templates draw A card had ONE caret, in its header, which collapsed the whole card - and Labels, Date Format, Members, Dependencies, Sort, Custom Activities starts collapsed and everything else open, because a card is The eye is gone, and that needed one piece of care: it was NOT the same control Custom Fields is one section with one caret however many fields a card has: its In an opened card's Dependencies section each row is an icon, the linked It wraps now, and the row grows as tall as it needs to. Two properties, and the Clicking a card's title opens the title editor in place of the card header - and The editor draws the X itself now, reusing the header button's own class, so it The same one-line omission was in the Requested by and Assigned byCustom Fields is reachable on a card that has none, which is where it is needed. Thanks to xet7.
own Custom Fields heading - the picker of which fields are on this card, a
pencil each, and Add custom field - and the card menu's entry, the Board
Settings row and the wrapper popup went with it. But the heading itself was
drawn only when the card already HAD a custom field on it.
custom field had no heading and therefore no hamburger; a board that had never
used them had no way in anywhere, because the sidebar view that still holds the
create and edit forms is opened by nothing. It is invisible on any board that
already has a field - which is every board a developer tests on - and the
browser tests found it on a seeded board that had none.
condition the rest of the editable card uses. The FIELDS below it still come
from the card's own values, so a card with none shows an empty section rather
than a phantom row per board definition, and a reader who may not write sees no
heading at all.
label text and left the tag, so it rendered nothing, could not be clicked, and
still had two handlers bound to it. tests/customFieldsSectionMenu.test.cjs
gains the two checks that would have caught this - the heading is gated on who
may write rather than on what the card already has, and no empty anchor or
orphaned handler is left behind.
A custom field made from a card is actually created, and its pencil is beside its name. Thanks to xet7.
list of fields, so its data context is the popup's own - which is the CARD - and
the create and edit forms are one form that decided which it was doing by
whether its context had an _id. A card has one, so creating a field from a
card ran the UPDATE branch against a custom field whose id was a card's: no such
document, nothing written, and nothing said so. The same form works from the
board sidebar, where the context has no _id, which is why this only ever
happened from a card.
field is made from nothing. And the form no longer trusts a bare _id: it asks
whether that id NAMES a custom field, so a context arriving from anywhere cannot
make it update one that is not there.
Every other pop-over list has ONE anchor per row, so the row is a block and the
anchor fills it - and two anchors in a block stack. This list has two by design,
the name with the checkbox that puts the field on this card and the pencil that
edits the field itself, so the row is the flex container now and the name takes
the space the pencil does not. Scoped to this popup, so no other list moves.
Ten popups had no header, and so no close button. Thanks to xet7.
no-title: no header, no X, no back arrow - Escape or a click away are the only
ways out. Noticed on the question "Are you sure you want to delete this
background image?", which is the worst place for it.
already were: a <name>Popup-title key. Delete Background Image, Delete
Duplicate Lists, Delete Account, Add Domain, Remove Domain, Map to existing
user, Export swimlane, Export list, Export checklist, and the member popup.
placeholder a pull would produce - the files are one key order, and a key
inserted in some and appended in others makes every later diff unreadable.
Popup.open(name, { titleKey }), is not a second way but
the same one pointed at a phrase the app HAS - "Custom Fields", "Sort Boards",
"Show on Card" - so a word already translated 147 times is not copied into a
new key that would start as English in all of them.
tests/popupTitles.test.cjs walks every popup
template and fails if one resolves no title at all, so the next one cannot ship
without a header.
Upload background image did nothing, and every attachment upload now shares one config. Thanks to xet7.
an upload's config are not optional in the way they look, and the board
background's config - written by hand - had neither:
fileId, and the same id copied into meta.fileId. Attachments'
namingFunction is what decides the name a file is STORED under, and on the
client it reads that id out of meta and deletes it. Without it the name is
undefined.
transport. HTTP everywhere, because Meteor-Files' default DDP floods
the WebSocket and makes Safari reconnect at ~95%; DDP on Sandstorm, whose
http-bridge strips the x-* upload headers so every chunk comes back "Can't
continue upload, session expired" [408].
attachmentUploadConfig.js, used by the
card's file picker, the card's pasted image and the board background alike - so
the next uploader gets them by asking for a config rather than by knowing two
things nothing would have told it.
pictures uploaded to this board - and Board Settings / Board backgrounds is
gone with the popup it opened: a second entry to a list that is already on
screen is a second place to look for one thing. Clicking a PICTURE, or its
name, puts that one behind the board; it used to be a 14px check icon in the
row under an 80px picture of the thing it applies, so the obvious click did
nothing. Download and delete stay in that row, being the two things a picture
cannot say.
fixed with it. insertAsync can reject BEFORE there is an uploader to listen
to, and that rejection went nowhere: the spinner stopped, no message appeared,
and the picture simply never turned up. It is caught and shown now. Each
picture is listed with its NAME, because two photos are the same picture at 80
pixels and nothing said which one had arrived. And a finished upload puts
itself behind the board: "add background image" is asked for by somebody who
wants that picture there, and an upload that only lands in a list, with the
board unchanged, reads as one that did not work.
An avatar with no name to draw broke the render around it. Thanks to xet7.
getInitials() ended with this.username[0], so a user document that arrived
without a username threw a TypeError - from a Blaze helper, mid-render. Two
helpers call it, so each avatar threw twice, and the second throw left the SVG's
viewBox half-written (0 0 15), which Firefox refuses outright: the avatar
drew nothing and took the rest of that render pass with it.
if there is one, then an empty string - a blank circle rather than a broken
page. A fullname of only spaces used to spell the literal word "undefined" in
the circle, because an empty word still contributed its missing first letter;
empty words are skipped.
The Date Format menu is back: two collapse mechanisms were folding one field. Thanks to xet7.
it back - the Received, Start, Due and End dates under the same heading were
there, but the selector above them was gone.
gave every FIELD of an opened card a caret on its own title and hid everything
after that title when it was clicked, storing the fold per card in the user's
profile. The section carets that replaced it put ONE handle on each section's
heading - and that heading is drawn on the first field of the section, so a
click on it was also a click on a field title. The old handler ran too, folded
that field, and stored it. Reopening the section brought the dates back but not
the field the heading sits on, and the per-field caret had been suppressed
inside a group, so there was nothing left to open it: the Date Format select
was hidden for good.
siblings, and the per-card state it re-applied on render. Every field lives in
a section now, and the section's heading is the only handle. The same per-user
store still belongs to the CHECKLISTS, which key their own entries by checklist
id and are untouched; that is why the store itself stays.
What the card restructuring nearly took with it, and a Copy card link button that can be read. Thanks to xet7.
failing: the file still compiles, the tests still pass, and a card is quietly
missing a row. Comparing the card against its state before the section work
turned up four things, three of them lost:
+cardButtons) - a slice used it as a boundary and
swallowed it;
+cardCustomField renders the value and nothing
else, so a card with three custom fields showed three values with nothing to
say what they were;
setting for it still governs;
the End date's add button.
else.
.btn, so it fell back
to the plain grey button whose dark label is nearly unreadable on a dark theme.
It is a .primary now - the board's accent with white text - named in the same
rules as the other themed buttons rather than given a copy of them.
A group's caret is on its first field's title, so the titles read as one row. Thanks to xet7.
named after its first field that line said the same word twice, one above the
other: "Labels" and then "Labels, Stickers, Location".
Stickers Location, Members Assignee Creator, and their + buttons on
the row below. The caret belongs to the first field's title rather than to a
header above it, and everything it folds - that field's own content and every
field beside it - is behind the same switch, so a collapsed group is exactly one
line.
card. The Checklists icon is a plain check again rather than a check in a
box, which is what it was before it became a section.
Requested By and Assigned By are set with a + like Members, and survive an import. Thanks to xet7.
have the round + the two fields beside them use now, opening the same editor
the text does.
is the worst shape this kind of bug takes - the export file looks complete and
the import reports success. They were exported by the card PDF, the card Excel
and the board CSV, and imported by NOTHING: a card exported and imported back
came home having forgotten who asked for it and who assigned it. Both importers
put them back now, our own JSON and .zip and the per-menu one.
columns - header and value together, since a header without its value shifts
every column after it.
Reporter is who asked for the work, and an issue's author on GitHub,
Gitea, Forgejo and GitLab is the same thing. Both arrive as Requested By, as
free text, so they survive an import from a tracker nobody on this board has an
account on. A source with no such field - Trello, CSV - gains nothing, which a
negative test pins.
Assignee and Creator sit beside Members, with their + buttons on one line. Thanks to xet7.
each a full-width row, so Assignee and Creator fell to the line below Members -
the class that made a FIELD full width was from before the groups, and the group
is the full-width row now. And a field whose title had been taken over by the
group header had its + on the first line, level with its neighbours'
titles, instead of on the second line with theirs.
family and folds it; the field's title names the field, and its content - the +,
the avatars - starts on the line under it, level all the way across.
A card's fields fold in GROUPS, from one caret each, and Members reads Members, Assignee, Creator. Thanks to xet7.
Location sitting under it, and every field had a caret of its own on the right -
#1591 draws one on each
.card-details-item - so a row could have two handles saying the same thing in
two sizes.
inside a group have none: #1591's per-item caret is turned off there, and its
title is no longer a handle. Outside a group it is untouched, so nothing that
folded before stopped folding. A collapsed group is one line - the caret, the
icon and the name - because everything else is inside the fold.
for. The loose rule the layout drew above the users block is gone with it: the
group's own rule is above the whole group, so there is one line there instead of
two.
The rules between card sections are the page's own line, and there is one where there should be. Thanks to xet7.
none of them visible in the source.
above Creator. It is that same lighter hr now - the section rule sets only its
spacing, so there is one line style on the card rather than two.
it to be separated from. Members had one too, immediately under the rule the
layout already draws above Creator - two lines with a heading between them.
Neither draws one now, and every other section still does.
above it, because .activity-title is display: flex and a rule inside it is a
flex item. The heading sits outside that row now.
A section is one full-width row with one rule, and is named once. Thanks to xet7.
moment a card was opened.
.card-details-items is a wrapping flex row, and
the
rule was drawn INSIDE a section - so it was as wide as that box, which is a stub
of a line beside a heading rather than a separator across the card. The six
sections that live in that row are now rows of their own, so the rule in them
spans the card, and the items that were laid out side by side on purpose -
Stickers, Location, the four dates, Creator, Assignees - still share rows as
they
did.
their own title, and the new section header drew it again, so a card showed each
of those names twice, one above the other. The templates keep what belongs to
the LIST - add a checklist, add a subtask - and the section header is the only
thing that names the section.
Every section of an opened card collapses from its own caret, with one rule between sections. Thanks to xet7.
Activities had an eye beside its heading. Every other section was a heading
with no control at all, and the rules between them were written by hand wherever
somebody remembered one.
Fields, Description, Checklists, Subtasks, Attachments,
Comments and Activities each carry a caret that collapses that section,
and one rule above it. Both come from ONE template, so eleven sections cannot
end
up with ten separators and nine carets. The heading is a real button - it
announces itself as one and Enter or Space works it - rather than a click target
only a mouse can find. The caret points DOWN when a section is open and toward
the way the reader reads when it is closed: right in English, left in Arabic,
Hebrew and Persian.
opened to read the card and its history is the thing you go looking for.
as the caret. showActivities: false - its default - subscribed to
activityType: 'addComment' only, so the eye chose between "comments" and "the
whole history" as much as it showed and hid. Removing it without more would have
quietly turned an opened Activities section into a comments-only list. So a card
section that is OPEN now asks for the whole history, and one that is closed
subscribes to nothing at all - which is the cheaper half of what the flag was
for, without a second control to keep in step. A card that had the flag
explicitly set to false keeps it.
header was briefly inside the loop that draws them, which would have been six
sections sharing a caret on a card with six fields.
A dependency's card title wraps instead of being cut off at the pane edge. Thanks to xet7.
card's title and its controls, and the title was held on one line with an
ellipsis. The card details pane is narrow and a card title is a sentence, so
most of a real title - everything to the right of the coloured icon - could not
be read at all.
second is the one that is easy to miss: white-space: normal alone would have
changed nothing, because a flex item's default min-width: auto refuses to
shrink below its content, so the text overflowed the row instead of wrapping
inside it. min-width: 0 is what lets it. A title that is one long token - a
URL, an id - breaks inside itself rather than pushing the type, colour and
remove controls off the row, and the icon and those controls now sit beside the
FIRST line instead of floating halfway down a three-line block.
Editing a card title keeps the X that closes it. Thanks to xet7.
the header is where the card's own close X lives, so the X disappeared the
moment you started editing and Save became the only visible way out. Escape
still worked; nothing on the screen said so.
is the same size in the same place and does not jump as the editor opens and
closes. That class floats to inline-end, which is the right in English and the
left in Arabic, Hebrew and Persian without a second rule - so the mirroring
needs no direction branch and must not grow one.
editors right below it: the close anchor was there, its icon was not, so the
click target existed and was invisible. They have their X now too.
Card export - what it says, and in whose language, format and time zone.
#6586 was reopened for what the Two exports of one card that disagree about what is on it are one bug reported Inside the PDF exporters one "The marked due date - here the time is not in the user set timezone (-2h wrong Dates are stored in UTC, and a WeKan profile carries no time zone at all, so the Both exports format through one helper, "Would it make sense to support markdown formated text in description? (so it A description is now cut into RUNS - What has no face is not invented: A card's PDF and Excel exports carry the same fields, under the same translated labels. Thanks to Heart1010 and xet7.
first fix left: the PDF export of a card had grown separately from the Excel
export of the same card, and every point the reporter came back with was a
symptom of that. The labels were hard-coded English - "Assignee, Labels, due,...
these titels should be in the user set language". They were not even consistent
with each other: the card export wrote Due: , the board export wrote due
with no colon and a lowercase letter. And a card carries more than either export
printed - "I think all those other things we set in a card should be also
present
in the pdf? Location, Voting, Checklists, Subtasks, Custom Fields, Attachments,
Comments...".
twice, so the fix is to stop them being two things. Both now carry the same
fields, in the same order, under the same i18n keys, in the language the request
carries: title, labels, creator, assignees, members, board, swimlane, list, card
number, requested by, assigned by, the six dates, spent time, description,
custom
fields, checklists, subtasks, comments, attachments, voting and Planning Poker.
The three sections neither export had - custom fields, voting and
poker - are new on both sides, selectable like the others in the Excel
export's field checkboxes, and appended to that list rather than inserted, so a
saved ?fields= link still asks for what it always asked for.
field() helper writes every Label: value, which
is what makes the board export's "due" and the card export's "Due:" impossible
to
have at once again. Every label carries its English text as the fallback, so a
language that has not translated a key shows the word rather than the key.
Exported dates are in the reader's own time zone and date format, not the server's. Thanks to Heart1010 and xet7.
for Europe/Berlin)." It was worse than -2h: the PDF export printed UTC and the
Excel export printed whatever zone the server process was started in, so the
same
card could come out with two different times on it and neither was the reader's.
only place the reader's zone exists is the browser. Every export link now sends
it - Intl.DateTimeFormat().resolvedOptions().timeZone, the IANA name the
server's own Intl wants back - together with the date format the opened card
is showing, which for a reader who is not logged in lives in localStorage
where no server-side lookup can reach it. An export that printed 2026-08-14
for
a card showing 14-08-2026 was the same card in two formats.
formatDateByUserPreference, which
gained an optional zone; without one it still renders in the process's own zone,
which is what every client-side caller - the card view itself - wants. A
server-built export that is given no zone renders UTC and SAYS UTC, rather than
printing the server's and looking like the reader's. The route accepts only the
three formats that helper understands, and a zone name is length-bounded: they
are request parameters, not free text. 12:00Z now prints as 14:00 for
Europe/Berlin, as 00:00 on the 15th for Pacific/Auckland, and a zone the
runtime does not know falls back instead of failing the download.
A description's markdown is drawn as bold and italic instead of being stripped. Thanks to Heart1010 and xet7.
gets transformed correct in the pdf output with bold, underline,....)" The first
fix removed the syntax and kept the words, because **bold** in a PDF is four
stray asterisks; this is the other half.
**bold** into a bold run, *italic* into
an italic one, ***both*** into both, nested emphasis keeping both - and each
is
drawn in the matching Courier face: Courier, Courier-Bold, Courier-Oblique,
Courier-BoldOblique, all base-14, so no font binary is embedded. Nothing is
measured to place them: consecutive Tj operators continue at the current text
position, so a font switch between two of them lands the next run exactly where
the last ended, whatever the glyph widths are. Wrapping counts characters across
the whole line, indent included, so an emphasised word does not push a line past
the page.
~~strikethrough~~ and `code` keep
their
words and lose their markers, because a base-14 Type1 font has no strike and
there is no fifth Courier to give code. Block markdown is still flattened either
way - a heading loses its # and is drawn in the bold font, a bullet keeps one
shape, a fence keeps its code. An underscore inside a word stays an underscore:
file_name_here is an identifier, not three-quarters of an italic.
and has the following developer-facing changes:
Language loading - which of the 246 language files a visitor is sent.
That was true when checked and nothing pinned it. A singleThe browser downloads one language, and a test says so. Thanks to xet7.
imports/i18n/data/ is 37 MB across 246 files, every one of them reachable from
the client. The only thing keeping them out of the initial bundle is that each
entry in imports/i18n/languages.js loads its file through
() => import('./data/<tag>.i18n.json') - a call Meteor's dynamic-import
package code-splits into a module fetched on demand - and that tap.js imports
exactly ONE of them statically, English, so the interface stays readable when
dynamic import is broken (#6503).
import data from './data/xx.i18n.json' added anywhere on the client would
quietly ship that language to every visitor, and nothing about the app would
look wrong - it would just be a heavier download, which no other test measures.
tests/i18nLazyLoading.test.cjs checks the six things that have to hold
together: every registered language has a dynamic loader, languages.js pulls
in no data itself, tap.js statically imports English and nothing else, the
loader is called once for the single resolved tag rather than mapped over the
registry, no client file bundles a language file, and dynamic-import is still
in .meteor/packages. A negative test proves the detector really sees a static
import, so the other checks cannot pass by failing to look.
Browser tests - the guards that drive a real browser, and what they say.
A run failed the same four tests in Chromium and in Firefox, and three of the The Activities heading in the board sidebar carries TWO icons since the caret The fourthThree page objects still described the markup as it was before it moved. Thanks to xet7.
four were the guard describing the OLD markup rather than the app being wrong.
editTitle waited for both js-card-title and js-open-inlined-form on ONE
element. The title splits now - the leading half opens the editor and the rest
of the heading drags the window - so the class is a DESCENDANT of the title, and
the old selector matched nothing and waited out its timeout.
became shared with the card sections: the caret that says whether the section is
open, and the section’s own comment icon. A bare i.fa matches both, which
Playwright fails as a strict-mode violation rather than picking one. The spec
asks for the caret specifically now - the three directions caretClassFor can
produce - which is also a stronger assertion, since the caret is the part that
indicates state.