A rebase rewrote the commits these entries link to, so the links named commits
that no longer exist and would have 404ed on GitHub. Repointed by
releases/fix-changelog-hashes.sh, which only touches a link no ref in this clone
can reach.
Thanks to xet7 !
releases/bump-bundle-npm-deps.mjs raises the nodemailer inside Meteor's email
package from 8.0.3 to 10.3.1 (dad03959d9), and nodemailer 10 moved lib/ to
dist/cjs/. packages/email.js, compiled against 8.0.3, links
lib/nodemailer.js and lib/well-known/index.js and Npm.requires
lib/mail-composer by path, so v12.95's build-amd64 failed its smoke boot:
"Cannot find module .../meteor/email/node_modules/nodemailer/lib/nodemailer.js".
releases/bundle-npm-security-bumps.json now shims nodemailer the way it
already shims uuid 11: lib/nodemailer.js and every lib/<name> directory the
exports map lists are written back, each forwarding to dist/cjs, and
lib/package.json marks lib/ as CommonJS because nodemailer is "type":
"module". A fresh local bundle taken through build-amd64's install, bump,
trim and prune steps now passes releases/bundle-smoke-boot.sh.
tests/bundleNpmSecurityBumps.test.cjs applies the shim to node_modules'
nodemailer 10, requires the recorded paths, checks that every exported
lib/<name> has a forward to the same module, and fails without the CommonJS
marker.
Thanks to xet7 !
d8b3579117 added server/methods/attachmentUploadPolicy.js but nothing
imported it, so the method did not exist: the card's
Meteor.callAsync('attachmentUploadPolicy') failed, the client treated that
as "not blocked", and the "+" stayed. server/imports.js now imports it with
the other server methods. tests/serverModulesLoaded.test.cjs, which failed
on it, passes.
Thanks to Raymond-Voogt and xet7 !
The opened card's + button picks board members into card.requesters /
card.assigners; the free-text requestedBy / assignedBy is optional. The
minicard only drew the free text, so a requester picked the usual way never
showed, although Show on Minicard was on. It now draws the picked members
as avatars beside the typed name, as the opened card does.
Card Settings showed Requested by with a user-plus icon and Assigned by with
a shopping cart, the other way round from the opened card and the minicard.
The settings rows now use the cart for Requested by and user-plus for
Assigned by.
tests/minicardRequestedAssignedBy6872.test.cjs pins the avatars, the
either-or condition and one icon per field across all three places, and
fails on a minicard person block gated on the free text alone.
tests/playwright/specs/minicard-requested-by-6872.e2e.js drives it.
Thanks to mueschel and xet7 !
Fixes#6872.
A pull request contributor, in wekan/wekan and in the maintainer's
repositories under .tools/, sends each feature or fix as its own pull
request, with only related changes in it, and changes no changelog: the
maintainer writes the entry after merging, when there is a commit to link.
The CHANGELOG section and the security CRITICAL entry say they are
maintainer mode only, and "good practice for everyone" no longer includes
the CHANGELOG structure. tests/changelogFormat.test.cjs checks both files.
Thanks to xet7 !
Return a JSON not-found response when the feature update lookup finds no organization or team. Cover missing records, successful updates, authorization, and reactive People-table checkboxes.
Thanks to AhmedLukman and xet7 !
Three tests registered the hook that removes their temp directory before the hook
that stops their engine. node:test runs after-hooks in the order they were
registered, not last-in first-out, so the directory was gone when stop() sealed
into it, and the ENOENT (files/index.ndjson) surfaced after the test as
"generated asynchronous activity after the test ended" - which failed the whole
suite although all 8 tests passed. One hook now stops the engine first and then
removes the directory. Five runs in a row, and the suite runner, pass.
Thanks to xet7 !
How: Admin Panel / Attachments / Transfer limits, set "Attachments upload" and
"API upload" to Blocked. That is the site-wide "Disable attachments" the issue
asks for, as two settings instead of one checkbox.
Comment for the issue:
A site-wide switch for this already exists, so a new "Disable attachments"
setting is not needed. Open Admin Panel / Attachments / Transfer limits and set
"Attachments upload" and "API upload" to Blocked. While they are on, the server
refuses an upload on every path - the upload pipeline, the api.attachment.upload
and api.attachment.copy methods, and the /api/attachment upload,
upload-background and copy routes - on every board, including boards created
later, and no board's allowsAttachments flag has to be touched. Download, move
and delete are not affected, so files uploaded earlier stay reachable. What was
missing is that a card still showed the "+" button and accepted a drop for an
upload that would then be refused; since d8b3579117 the card asks the server
once a minute and hides both. There is no second setting, so nothing can
disagree with what the server enforces. Two settings instead of one checkbox
is the one difference from the request.
Thanks to Raymond-Voogt and xet7 !
Fixes#6870,
With several WeKan servers on one database, every server applied the continuous
backup setting, so each of them tailed the oplog and wrote its own archives into
the same target directory (#6861, item 3).
The leader holds a lease: one continuousBackupLease document with its id and an
expiry. Taking it is one atomic operation (an insert while there is none, else
an update that matches only an unheld, own or expired lease), the leader renews
it every 15 seconds and it lapses after 45, so another server takes over within
a minute when the leader stops. A server that loses the lease stops its stream.
Startup, saving the settings and a change saved on another server all converge
through that loop, and a restore or a fetch from the cloud takes the lease and
waits one renewal so it is the only writer. One server is always its own leader.
Checked against the real driver on FerretDB (SQLite): 24 clients at once give
exactly one leader; renewal, expiry, loss of the old leader and release behave.
tests/continuousBackupLease.test.cjs pins the same and the wiring in
server/continuousBackup.js. docs/Backup/Continuous-Backup.md no longer says
single-server only.
Thanks to Raymond-Voogt and xet7 !
Refs #6861, not closed: the remaining items of the issue were fixed in 4e28465964.
#6870 asks for a site-wide switch that turns attachments off, because the
per-board allowsAttachments flag has to be set on every board. That switch is
there already: Admin Panel / Attachments / Transfer limits, "Attachments upload"
and "API upload" set to Blocked, and the server refuses on every path - the
upload pipeline, api.attachment.upload and .copy, and the /api/attachment
upload, upload-background and copy routes. What was missing is that the card
still showed the "+" button and accepted a drop for an upload that would be
refused.
attachmentUploadPolicy tells a signed-in client the one boolean (nothing about
the limits), asked at most once a minute, and the "+" and the drop/paste upload
follow it. No second setting is added, so there is nothing that could disagree
with what the server enforces. tests/attachmentUploadPolicy.test.cjs pins the
client, the method, and the server enforcement it relies on, and that no
"disableAttachments" setting exists.
Thanks to Raymond-Voogt and xet7 !
Refs #6870, not closed: the reporter asked for a single Features checkbox, and the
two Blocked limits are two settings.
PUT /api/boards/:boardId/integrations/:intId answered 200 with the requested id
when no integration matched that id and board (PR #6871). The two activities
routes (POST and DELETE .../activities) had the same fault: they updated
nothing and answered 200 with `data: null`. All three now look the integration
up on that board after board-admin authorization and answer the JSON 404 the GET
route already uses (#6862), before validating or writing anything.
tests/restIntegrationUpdate.test.cjs and the Playwright spec are the PR's.
tests/restIntegrationWriteRoutes404.test.cjs adds the activities routes and
pins, for the whole file, that every handler that updates an integration looks
it up first and only after authorization.
Thanks to AhmedLukman and xet7 !
The board Excel export answered a missing board with HTTP 200 and the text
"Board not found" (PR #6869). The two PDF export routes had the same fault, and
the Excel and PDF routes answered an over-long auth token with 200 too, while
export.js already used 404 and 400. They now choose their status the same way.
tests/boardExcelNotFound.test.cjs and the Playwright spec are the PR's.
tests/exportErrorStatuses.test.cjs scans every models/export*.js route for a
text answer without a status, so the fault cannot return anywhere there.
Thanks to AhmedLukman and xet7 !
A Grype scan of the image found advisories in packages that Meteor's own
packages carry in programs/server/npm/node_modules/meteor/<package>, which
package.json cannot reach.
nodemailer 8.0.11 -> 10.3.1: meteor/email only calls createTransport, the
'stream' plugin hook, nodemailer/lib/well-known and nodemailer/lib/mail-composer,
and all four work in 10.3.1 (checked with the bundle's own package specifiers,
through its exports map). body-parser 1.20.6/1.20.8 -> 1.20.9 and 2.3.0 ->
2.4.0: a minimum may now be "name@MAJOR", so each major is raised inside
itself. qs -> 6.16.1, postcss-selector-parser 6.1.4 -> 6.1.5.
uuid 8.3.2 and 9.0.1 -> 11.1.1: 11 moved its entry point, which is why it was
refused before. The bumper now writes the recorded dist/index.js back as an
ESM re-export of dist/esm/index.js, which require() loads and which leaves
uuid's own ESM and CJS entry points as they are.
glob 14 and minimatch 11 are already in package.json. Run against a copy of
the published image's bundle, the bumper raises all of these and every raised
copy loads.
Thanks to the Grype scan reporter and xet7 !
The header wraps to several rows on a phone, far taller than the 8px the
floating card docks at, so the card's top row sat among the header's rows and
was only seen by dragging. Below 801px a card without an inline top, popup
cards included, starts at --wekan-header-height + 8px; a dragged place is an
inline top and is never overridden.
Thanks to the iPhone 12 mini reporter and xet7 !
On iPad Chrome and Safari (both WebKit) the opened card's top row - the collapse
caret, the card number, the menu, maximize and X - was behind the header bar,
although the card has z-index 2001 and the header 1000. iOS WebKit makes every
scrolling element (#content, the board wrapper) a stacking context of its own, so
the card's z-index is compared only inside it while the header bar, a flex item of
body and a stacking context too, is painted over all of it. While a card is open
the header bar (#header-quick-access) is now at z-index 0, the level of the
content, which comes after it in the page and so paints over it; with no card open
it keeps 1000.
A browser spec makes #content and the board wrapper stacking contexts with
isolation: isolate, which is the same trap, and shows the card over the header open
and collapsed, its close button reachable, in Chromium and WebKit; without the rule
it fails with the card's top at 8 behind a header 88 high, as in the screenshot.
Thanks to xet7 !
app/board-open.cjs seeds a board of 200, 2000 or 5000 cards, opens it in Chromium and
records the wall clock to the first card and until the DDP WebSocket is quiet, what
crossed it, the Meteor server's CPU by who spends it (Node inspector) and FerretDB's
CPU (pprof). Two runs on each database: the Meteor server's CPU is the same on both
(0.1-0.5 s) and the DDP server is 1% of it; a 5000-card board takes FerretDB (SQLite)
400-600 ms longer to open, the data sent to the browser being the same ~210 KB, a
small board 70-100 ms. FerretDB uses about 4 s of CPU for the 5000-card open: SQLite
39%, filtering in Go 34%, find/getMore/aggregate 0.7-0.8 s each. The README says what
was measured and what was not: the development build, one machine, the browser's own
time not separated, and the aggregate's source not identified.
Thanks to Raymond-Voogt and xet7 !
On an iPad the opened card did not move when dragged by a finger from the collapse
caret, the space beside it, the card number, or the drag handle, while the page
behind it scrolled. The card was moved by mousedown / mousemove / mouseup, and a
finger sends none of them while it moves; the drag areas also lacked
touch-action: none, so the browser took the gesture for a scroll. The card window
is now moved by Pointer Events: a press on the header row or the handle moves it, a
press on a link or button in the row (the caret, close, the menu) waits for a real
drag so a tap still does its job and a drag does not also collapse or close, the
title still edits, and with drag handles on the handle alone moves the window. The
card number is above the title's edit zone so it drags too, the window is kept
within reach, and touch-action: none is on the card header, its handle and every
popup's title bar and handle. Blaze hands over a jQuery event without isPrimary, so
the native event is read.
A browser spec drags with a real touch (DevTools protocol, Pointer Events) from the
caret, the number and the space beside the title, with handles on and off, a tap
that still collapses, a mouse that still drags and a popup; without the fix the
finger tests fail.
Thanks to xet7 !
A card opened as a popup (a search result, a table row, a Roadmap/Gantt bar) got
the generic wide-layout rule top: 97px and z-index 30, because the desktop-mode
rules that dock an opened card at the top (top: 8px, z-index 2001) skip a popup.
So it opened BELOW the header, and collapsed it was a bar floating over the
swimlane, while the same file's popup wrapper rule says it must cover the header
bars. The popup card now docks at top: 8px with z-index 2001 in the wide layout;
mobile mode keeps its own full-window rule. A browser spec shows the card over
the header, open and collapsed, in Chromium and WebKit, and fails without the fix
(top 97 against a header 88 high).
Thanks to xet7 !
The same button leaves full screen; its title and name were still Maximize, which
does not say so. The existing Minimize translation is used, so no language file
changes.
It puts the whole page in full screen with the Fullscreen API (the unprefixed one
and the webkit-prefixed one) and takes it out again; the icon follows the
browser's own events, so Esc is understood. The label is the existing Maximize
translation, so no language file changes. It is drawn only where the browser
offers the API and the page may use it: not on iPhone, not in an iframe without
allowfullscreen. Unit tests drive the pure part with fake documents, including a
refusal; a browser spec checks the position, the label, entering, leaving, Esc,
and that nothing is drawn where the API is not enabled, in Chromium and WebKit.
Thanks to xet7 !
minimatch before 11.0.1 (GHSA-g3hv-75wg-m249): npm-packages/exceljs still had
10.2.6 through readdir-glob 3, and the vendored jade lockfile under
meteor-jade-loader had three copies through glob 13 and test-exclude; both get
an override to minimatch 11, as the root lockfile has. A test now reads every
package-lock.json of the repository, not only the reported ones.
The benchmark module of embedded engines pulled klauspost/compress 1.18.0,
golang.org/x/net 0.23.0 and golang/glog 1.0.0 (GHSA-259r-337f-4rfw,
GHSA-5cv4-jp36-h3mw, GHSA-qxp5-gwg8-xv66, GHSA-vvgc-356p-c3xw,
GHSA-6wxm-mpqj-6jpf). They are raised to 1.18.7, 0.62.0 and 1.2.4, and
govulncheck then found more in x/net, x/text and OpenTelemetry, which are raised
too (x/net 0.62.0, x/text 0.43.0, otel 1.42.0): it now reports none. A test
holds every go.mod of the repository at or above these floors.
Thanks to dependabot and xet7 !
A benchmark of its own (docs/Databases/FerretDB/1/SpeedTest/embedded, a Go module,
no cgo) loads the speed test's cards into SQLite set up as FerretDB sets it and
into Pebble, Badger, bbolt, BuntDB, Nutsdb and Clover, and asks each the six
questions WeKan asks of cards, at 15,377 and 153,768 cards. A board's cards take
the same time on every engine that is not slower, because decoding the JSON
dominates; the key-value stores win single reads and writes by microseconds that
a FerretDB call hides; Pebble is 12x slower on fetching a board, Clover 90-190x
slower in total, and bbolt with its default fsync 100-160x slower on writes. The
README accounts for every entry of the awesome-go section.
Thanks to Raymond-Voogt and xet7 !
Dolt runs under the existing MySQL handler with no new handler and gives the same
answers, but is by far the slowest: 20,470 ms in total, every card query a
2.8 s whole-table scan. The VersionHistory scripts drive WeKan-shaped changes
through FerretDB onto Dolt (a commit per write, undo and redo with DOLT_REVERT,
AS OF, +0.4 ms a write, a conflict when the same document is restored again) and
through the REST API of TerminusDB (author and message per commit, field-level
restore). ClickHouse and StarRocks were tried with the MySQL handler and need a
handler of their own; the README says where each fails.
Thanks to Raymond-Voogt and xet7 !
TiDB (MySQL protocol, Apache-2.0) runs FerretDB after its metadata table's
generated columns became VIRTUAL, and gives the same answers as MongoDB; it is
a speed test row and opt-in for the conformance run (WEKAN_CONFORMANCE_TIDB=1).
Noria has no JSON type, triggers or generated columns, which the MySQL handler
needs; YugabyteDB refuses a CREATE INDEX on a jsonb expression, which every
FerretDB v1 PostgreSQL index is; CockroachDB is not open source. The README
also records that analyzing a PostgreSQL table after its indexes are created
took the PostgreSQL row from 454 to 145 ms, and that the same on MySQL and
MariaDB did nothing.
Thanks to Raymond-Voogt and xet7 !
Results.md now opens with an At a glance table (rank, total, how many times
slower than the fastest, the same for the slowest user), every table is ordered
by its own numbers, the header says the real data size and the real operating
system, and the load table is ordered by the whole load time. pgrust
(PostgreSQL in Rust, malisper/pgrust:v0.4-beta) is one more row through the
same PostgreSQL handler; in the conformance run it is opt-in with
WEKAN_CONFORMANCE_PGRUST=1. The run is 1/20 of a production WeKan so it takes
minutes.
Thanks to Raymond-Voogt and xet7 !
The ten newest of 30,116 users took 52 ms with an ORDER BY in the database and
38-49 ms decoded and sorted in Go, so the change was not kept; the README says
what was measured so it is not repeated.
Thanks to Raymond-Voogt and xet7 !
SQLite sorts and stores JSONB, PostgreSQL uses a GIN index for membership and
pushes down $or, $elemMatch and $and, MySQL and MariaDB read through their
indexes with multikey side tables, and WeKan stopped scanning whole collections
every hour or at every start. Results.md still shows v1.99.0, which the README
now says, and the open items are updated to match.
Thanks to Raymond-Voogt and xet7 !
GET /api/boards/:boardId/cards/:cardId/checklists/:checklistId answered 500 with
no body when the checklist did not exist or belonged to another card. It now
answers 404 with the same error text the checklist update and delete routes
use; board access and the parent-card check are unchanged. Route tests drive
the registered handler (found, missing, other card, other board, denied, lookup
failure) and a browser spec reads the checklist over REST and after a reload.
Thanks to AhmedLukman and xet7 !
tests/boardLabelLookup.test.cjs sliced the getLabel method out of models/boards.js
and then ran .replace(/^getLabel/, 'getLabel') on it, which does nothing: the
slice already starts at getLabel. tests/noIdentityReplacement.test.cjs skipped
every regular expression with an anchor, so it could not see it; a leading ^ or
a trailing $ over plain characters is now compared like any other.
Thanks to GitHub CodeQL and xet7 !
Lay-out has a new Login page background color (loginBackgroundColor). Only a
hex colour is accepted, so the value in the CSS custom property can never carry
CSS; the text under the login logo turns white on a dark colour and black on a
light one. The colour is on body only while the sign-in layout shows. The
support page and the text under the login logo get a centred column with sized,
bold headings and space between paragraphs. The label is translated into every
language file.
Thanks to Raymond-Voogt and xet7 !
Fixes#6866,
The belize and nephritis buttons, the limegreen header, the red-string and
typed-relation lines are now the darker shades that keep their contrast, and
the checklist drag released on the sortable's own bottom edge.
Change Settings has Show focus outline, on by default: the blue ring round the
focused control is an accessibility feature (WCAG 2.4.7), and it also shows
after a click on a minicard. A member who turns it off - profile.hideFocusOutline,
a toggle method that takes the signed-in user only - gets a class on body that
hides the outline and its halo for them and nobody else. The label is translated
into every language file (best effort; the low-confidence ones are listed in the
changelog).
Thanks to mimZD and xet7 !
Refs #6755,
The composer opened after a list (or in an empty swimlane) stays open after a
list is created, so several can be added, and only its X closed it. Escape now
closes it at the level of an inlined form, with the same reset as the X, and not
on a click elsewhere.
Thanks to mimZD and xet7 !
Fixes#6865,
On a Farsi (or Cyrillic) keyboard the d shortcut opened the Due date popup and
closed it at a glance. keyboard.js re-dispatched every key typed on another
script as its Latin key, a leftover from Mousetrap; hotkeys-js resolves the key
from event.code itself, so the shortcut ran twice and the repeat clicked the
same control again, closing it. The re-dispatch is removed, and a test drives
real key events for Latin, Farsi and Cyrillic at the library.
Thanks to mimZD and xet7 !
Fixes#6840,
Eight themes (nephritis, pumpkin, moderatepink, strongcyan, limegreen, relax,
corteza, modern) had white text on an accent at 2.1 to 4.3:1: their accents and
hover, selected and emphasis shades are darkened until it reads at 4.5:1. The
filter sidebar's headings, the clear themes' quiet text and cleanlight's
add-dependency link are darker. A color a member chooses is made legible: a
custom theme accent or slide end is darkened under the header's white text, the
text color is moved to read on the theme's surfaces and no longer reaches the
header, labels and primary buttons, a dependency line is drawn at 3:1, Kanri's
tag text is the better of dark and light, and a custom label, card, list or
swimlane color already derives its text. All 25 themes show no colour contrast
failure on the board, the sidebars, Card Settings, the card and its popups.
Thanks to Samunosuke and xet7 !
A label is looked up by name and color before one is added, and the lookup
compared the name as sent while the schema stores it trimmed: a padded ' Bug '
missed the stored 'Bug', and each repeat created another label with the same
name and color. The REST builder now trims, as the pull request did, and so
does the lookup itself, which the card's label form, the importers and the list
sync share. A legacy label stored with padding is still found.
Thanks to AhmedLukman and xet7 !
The accessibility audit made the quiet text greys dark for every theme, and
old rules never followed the dark themes: Card Settings drew white text on
light grey blocks, popup items, the sidebar's shortcuts row, quiet buttons and
sidebar labels were black on dark, and an !important list header beat each
theme's own. The quiet text is a variable with a light value on the dark
surfaces; Card Settings' sections, popup items and the other rules follow the
theme. axe-core finds no colour contrast failure in cleandark, moderndark, dark
and exodark on the board, sidebar, Card Settings, an opened card and the custom
fields popup.
Thanks to Samunosuke and xet7 !
Fixes#6863,
The scoped card GET (/api/boards/:boardId/lists/:listId/cards/:cardId) sent
200 with no body for a missing, wrong-list, wrong-board or archived card. The
list, swimlane, custom field, comment and integration GETs did the same, and
the user GET threw a TypeError for an unknown name. Each now answers 404 after
the board check, so a stranger still learns nothing about which ids exist.
Thanks to AhmedLukman and xet7 !
The notification cleanup ran at every start and daily on every server, reading
and rewriting the same users 24 times at a rollout; a notification added by
another server between a read and its write was lost. A run now claims a
document per job and day in dailyJobRuns, and the timer looks hourly for an
unclaimed day. The file integrity audit read 'lastRun', checked it, then wrote
it, so two servers could both scan; one atomic update now claims the run.
Continuous backup has no leader: that is documented as single-server, and the
lease it needs is not built, so #6861 item 3 stays open and this commit does
not close it.
Thanks to Raymond-Voogt and xet7 !
collection2 wraps a failed write as 'ValidationError: Failed validation <driver
text>', and the driver's 'Connection pool for <host> was cleared' names a
replica set member that MONGO_URL does not list, so the schema rule matched.
The text is now a lost connection. The board key backfill looked for boards
without a key with an $or including an exists-false branch, a scan of every
board at every start of every server; one $in on the key index finds the same
boards.
Thanks to Raymond-Voogt and xet7 !
Fixes#6860,
The default theme's blue is #2573a7, so the header's white and #f2f2f2 text
passes WCAG AA; label colours, quiet greys and grey tiles have text that
passes too. The Starred menu, the user menu and initials avatars carry their
visible text in their accessible name. The card's Pomodoro field and the
comment and description editors have a name. A card section heading is an h3
holding a real button with aria-expanded, and its menu and layout toggle sit
beside it. The sign-in form is the main landmark with a level-one title, every
page has a level-one heading, and the shortcuts list is reachable by keyboard.
After the fixes axe-core finds no violation on 22 pages. Spec 45 runs the scan
and tests/accessibilityAuditFixes.test.cjs pins each fault by its shape.
Thanks to xet7 !
npm run lint:promises runs typescript-eslint's no-floating-promises and
no-misused-promises over models/lib, server/lib and imports/lib, with type
information read from tsconfig.lint.json (nothing is type-checked). The
eleven findings were promises that never reject (their own catch) and now
say so with void, and three checks that used a promise as a boolean.
Thanks to xet7 !
Real faults: a card's original-position description formatted a promise
because it did not await the history; the attachment swipe distances and
two drag-scroll locals were undeclared globals; a file that cannot be read
during a GridFS upload left the upload open; api.py called exit() instead of
sys.exit(); a rules test redefined a property and a security-log test kept
an unreachable branch. Also 31 missing semicolons, unused imports in the
card details, exporters and attachment API, unused Python imports, and
comments on empty except clauses. The list-sync scanner test cut its source
at the last Meteor.startup, which the new index startup of #6855 had moved.
Thanks to GitHub CodeQL and xet7 !
An account found by username or e-mail that records another directory
entry's identifier (same attribute, other value) is refused at login and
skipped by the import. An existing account now records its identifier and
its attribute, so LDAP_UNIQUE_IDENTIFIER_FIELD takes effect for it.
LDAP_EMAIL_MATCH_REQUIRE looks users up by username instead of _id, which
never matched. The background sync skips an account whose username matches
several entries instead of binding it to the first.
Thanks to Raymond-Voogt and xet7 !
Fixes#6859,
#6815 checked the number of every new card, but a card also enters a board's
numbering by an update: a move from the browser, which computed the
destination number from the cards it had loaded (often none, so 1), a restore
from the archive, and a number set by the REST API or an import. A before-update
hook now does what the insert hook does: a taken number is replaced by a fresh
one from the board's counter, a free one is kept and the counter raised past it.
Thanks to Raymond-Voogt and xet7 !
Fixes#6858,
Every server read the same MongoDB log and counters and recorded each
finding itself: one collection scan became up to 24 identical rows in Admin
Panel / Problems on 24 replicas. A probe now claims the period in a shared
document, and the state that compares one probe with the next lives there.
A claim that cannot be made at all does not stop the probe.
Thanks to Raymond-Voogt and xet7 !
Fixes#6857,
minimatch before 11.0.1 grows its glob parts without bound while it
preprocesses a pattern, then compares them pairwise. archiver (readdir-glob),
@typescript-eslint/typescript-estree and @babel/cli's glob used 10.2.6; they
now use the root minimatch 11.0.4.
Thanks to dependabot and xet7 !
A row said only which fields a slow query filtered on, which is not enough
to find the query in the code or to run explain(). It now shows the command
(for a slow getMore, the query it continues, instead of 'filter on nothing'),
the operators used on each field - never values - and MongoDB's queryHash.
Thanks to Raymond-Voogt and xet7 !
Fixes#6856,
The startup schema re-check claims a run in _wekan_migration so one server
re-checks while the others skip (a claim older than 2 hours is taken over),
and a run that leaves work unresolved is retried once a day instead of at
every start. The daily due-date reminder scan is claimed per day in
dueReminderRuns, so 24 replicas no longer send 24 reminders per card.
A partial index on lists serves the 15-minute list sync scan, and a sparse
index on boards.dueReminderDays serves the due-date lookup.
Thanks to Raymond-Voogt and xet7 !
Fixes#6855,
The banner waited for membership of the current board, and Home, Remaining,
Starred, Templates and the other All Boards pages have none. It now shows to
every signed-in user there, and to the members of the open board on a board.
Signed-out visitors still do not see it.
Thanks to titver968 and xet7 !
Fixes#6854,
A list of years had to end somewhere. The year is now a whole number
(0 is 1 BC) found by jumping through the calendar in use, so thousands of
years back or ahead are one entry away, up to the range a date holds.
Thanks to brlin-tw and xet7 !
Fixes#6853,
The heading above the calendar shows the month and year only, not the
YYYY-MM-DD of the selected day. Under the calendar, year, month and day
drop-downs pick any past, present or future date, in every calendar
system.
Thanks to brlin-tw and xet7 !
Fixes#6853,
Changes since October whose commits lacked a unit, negative or browser
test:
- 2929b61c85: a new drawing refits when its window changes, and is left
alone once drawn on, edited, or zoomed or resized by hand.
- c64b3a3b1d: Due Cards, in "me" and "all users", shows an assigned-only
member only their assigned cards; the owner sees them all.
- 8a6aeba7d1: the All Boards dependency import says how many lines were
added, already there and unmatched.
- 5c50f38e25: a signed SAML Response with two Issuers is refused, which
node-saml 5.1.0 accepted; the same Response with one Issuer logs in.
The browser specs pass in Chromium.
Thanks to xet7 !
October Admin Panel changes whose commits had no test of their own:
- 85af731c1b and 2017f2871f: the Password length and Audit log panes
name themselves in the title bar, the audit log has its own URL, and
its cells align to the start - right in Hebrew - with no left/right
rule left in its CSS.
- e6f7d226ca: no help line of a storage field is shown twice.
- 77956c98ed: Data health's "Never" is its own key, also when an
administrator translates the List Sync one differently.
- 69f3358154: the card reports stay instance-wide and admin-only.
tests/playwright/specs/admin-pane-titles-and-help.e2e.js passes in
Chromium.
Thanks to xet7 !
October changes whose commits had no node test of their own:
- ffe49dd2fd: decryptFile() refuses both the OpenSSL 4 and the OpenSSL 3
wording of a failed authentication as "changed or the key is wrong",
on any OpenSSL; an unrelated error (ENOSPC) is passed through.
- 0218cd823f: the rollover and batch-job leases are claimed before every
write, another server's live lease is refused, and each lease field is
written in one place in the whole tree.
- 8cbe2605fe: on macOS, a stopped engine and one with watching off run no
catch-up scan.
Thanks to xet7 !
ecmascript implies the local dynamic-import fork, so compiling ddp-server,
listed before it in .meteor/versions, stopped the source-loaded server
with "isopack dynamic-import not yet loaded". The loader now loads the
local package asked for and retries. The source server still stops later,
on "Cannot find module 'meteor/accounts-base'"; the bundle server is what
the browser tests ran against.
Thanks to xet7 !
Chromium does not leave the focus on the minicard after its menu popup
is closed with Escape - the menu's opener is a link without href, which
cannot take the focus back - so the test asserted a state the browser
had not reached. It now focuses the minicard's link, the state of the
report, and checks that d still opens the Due date editor from there.
Passes in Chromium and WebKit.
Thanks to mimZD and xet7 !
The Rules page lost its Back with its own sidebar; only the trigger, action
and rule details pages kept one, to the rules list, so the way back to the
board was the Board View menu. The rules list and its Workflow, Bots,
Blocks and History views now have a Back link to the board at the top. The
three sub-pages keep their own Back and show no second one.
Thanks to mimZD and xet7 !
Fixes#6852.
Both are published on the Hall of Fame, so the inventory counts 143 and
names each one's unit and browser regression tests. CrossBoardExportBleed
was published without being added, which failed the whole node-suite run.
Thanks to xet7 !
An assigned-only member whose card assignment was removed kept the card and
attachment ids and, through POST /api/attachment/upload and
DELETE /api/attachment/delete/:id, still added a file to that card and
soft-deleted its existing one: both asked only for board write access. The
DDP methods api.attachment.upload/delete, and copy and move onto a target
card over REST and DDP, had the same shape.
mayWriteCardAttachment (server/lib/assignedOnlyAttachments.js) applies the
same card rule the ordinary card Delete already used to all eight handlers.
A refusal is recorded in Admin Panel / Problems as UnassignedAttachBleed
(authz.assigned-attachment-write, medium: never disables the account).
tests/unassignedAttachBleed.test.cjs reproduces the reported sequence and
fails if any attachment API write lacks the card check;
tests/playwright/specs/unassigned-attach-bleed.e2e.js replays it over REST.
GHSA-w26g-rc43-q6cm.
Thanks to Javier García Herrera and xet7 !
The shortcut filter refused every key while a link or button had the focus.
The minicard is itself a link, so a click on its date badge or its menu left
that link focused, and closing a popup returns focus to the button that
opened it: hovering a card and pressing d then did nothing until a reload.
A focused control now keeps only the keys it uses itself - Enter for any of
them, Space for a button, summary or checkbox, End/Home/arrows for a tab -
so Space still activates a focused button (audit A01) and every other key
is a shortcut again.
Thanks to mimZD and xet7 !
Fixes#6755.
Each test navigates to All Boards and at once opens Export all boards.
All Boards is still drawing just after the route changes, and in
EVERYTHING's full Firefox run the popup opened then was closed again: no
header title to read (the first test; it passed in the run before).
All five tests open the popup again until its title is there. The spec in
Firefox, three times beside the running suite: 15 of 15.
Thanks to xet7 !
The test waited until the drawing's viewBox had grown past the old 800,
then read the surface's box once. The element's layout follows the
viewBox a frame later, and in EVERYTHING's full Firefox run it was still
800 px against more than 1088 expected (it passed in the run before).
It polls the surface's width as well. The spec in Firefox, three times
beside the running suite: 9 of 9.
Thanks to xet7 !
saml-popup-error's first test failed in EVERYTHING's full Firefox run and
2 of 3 times alone: #login-error-message stayed empty after the SAML
button was clicked with a stub that fails at once.
The test clicked #at-saml (force) as soon as Meteor connected, while the
sign-in form was still drawing: the click landed on a button being
replaced, or - in another attempt - below the window ("Element is
outside of the viewport"), and no handler ran. It now waits for the form,
scrolls the button into view and clicks until the error shows. With one
worker both tests pass 6 of 6 in Firefox against a rebuilt bundle.
Found on the way: #login-error-message sits inside `if isLoading`, and
showLoginError wrote into the element - when that block is drawn again
the element is new and empty, so an error shown just before was lost. The
template now renders the message from a ReactiveVar (loginErrorText) as
escaped text, so a redraw keeps it and it can still never be HTML.
oauthProviders.test.cjs pins the same region, the translated codes and
text-only output in that form.
The other sign-in specs (44 files) pass in Firefox with it, apart from
runs where two copies of a settings-changing spec ran at once.
Thanks to xet7 !
The SMTP sink captures every mail the shared test server sends while it
listens. The test waited until user2 had exactly one mail, then read
sink.messages[0] - in EVERYTHING's full Firefox run another worker's
comment notification ("Please look", to another user) had arrived first,
so "Frozen recovery body 0" was looked for in the wrong mail.
It reads the mail addressed to user2. The whole spec in Firefox, twice:
24 passed, 2 conditionally skipped.
Thanks to xet7 !
The String Template help test opened a card and at once its custom field
settings popup. The opened card was still drawing, and in Firefox under
the whole suite the popup sometimes closed again before Create could be
clicked: ve-CC and rup failed in EVERYTHING's full Firefox run, 42
others passed.
The test opens the popup again until its Create button is there. ve-CC,
rup and en, twice each in Firefox beside the running suite: 6 of 6.
Thanks to xet7 !
All four Android bundle jobs failed: WeKan crash-looped on
No native build was found for platform=android arch=x64 runtime=node
abi=147 ... loaded from: .../meteor/accounts-password/node_modules/argon2
until the 240 s smoke test gave up. Meteor's npm rebuild had printed
"install scripts blocked because they are not covered by allowScripts",
though prepare-bundle-npm.mjs writes argon2 and bcrypt into every
rebuild directory's allowScripts.
npm 12 applies allowScripts to a project's DECLARED dependencies only.
The package.json that prepare-bundle-npm writes there declared none, so
argon2 and bcrypt were "extraneous" and their scripts stayed blocked -
reproduced with npm 12.2.1 on this release's bundle, and with the two
declared the same rebuild runs both install scripts. Linux, macOS and
Windows never noticed: prebuilds load there, and the Windows jobs compile
the two themselves. Android has no prebuild, and check-addon-arch passed
the Linux ones it cannot load.
prepare-bundle-npm.mjs now declares each reviewed installer a rebuild
directory has installed, at its installed version, and leaves a declared
one as it is. The Android build runs it itself first - the Linux bundle
it rebuilds may predate this - and loads argon2 and bcrypt with Android's
Node.js right after the install, so a missing addon stops the build with
its name instead of a smoke-test timeout.
prepareBundleNpm.test.cjs and androidBundles.test.cjs pin both; they fail
on the code before. Not run on Android here: the build needs Termux on
an Android-CPU runner.
Thanks to xet7 !
GHSA-8885-xrq4-j47h, reported by ccsimplyspolit, both findings confirmed
in the current code:
1. GET /api/boards/<board>/export?checklistId=<id> (and exportZip)
selected the scoped checklist and its items by id alone. A checklist
of another board - a private one too - was streamed with every item
and its creator, whose profile the anonymization map did not cover.
Both selectors are now bound to the exported board's cards, and a
checklist of another board is recorded in Admin Panel / Problems
(authz.export-checklist-scope).
2. The streaming CSV/TSV export asked neither Disable all export nor
Anonymize export users. Neither did, it turned out, Excel (board,
card, all boards), PDF (board and card), the chart exports, the
calendar feed or the external formats: only the WeKan JSON, ZIP and
Trello exports did, though the setting promises that "the server
rejects any export request" and names CSV and Excel.
- refuseDisabledExport() answers 403 before anything is written, and
all 13 HTTP export routes call it first; assertExportEnabled() still
guards the builders. Both record the attempt (authz.export-disabled,
never blocking the account: a page opened before the switch still
shows the menus).
- anonymizeExportIdentities() anonymizes what one exporter writes -
users, @mentions, requested-by / assigned-by, comments - with the map
build() uses, board members included. The CSV/TSV stream, Excel,
PDF, chart exports (forExport: the chart views keep names), the
calendar feed and every external format call it.
tests/crossBoardExportBleed.test.cjs drives the gate and the anonymizer
and scans the tree: no scoped checklist selected by id alone, no export
route without the gate, no exporter naming people without anonymizing.
All 7 fail on the code before. The new spec
security-cross-board-export.e2e.js replays the reporter's requests and
checks every export route while export is disabled and the CSV and
Kanboard exports while anonymizing; it passes with export-access and
scrum-export-anonymization against a rebuilt bundle.
Thanks to ccsimplyspolit and xet7 !
The splitter test measured the splitter straight after opening Card
Settings, while the popup was still placing itself. In WebKit the press
landed where the splitter had been and dragged nothing: the pane stayed
526 px against more than 606 expected (the first attempt in EVERYTHING's
WebKit stage; the retry passed).
It now measures once two reads of the splitter and the panes 150 ms apart
agree. WebKit 5 of 5, Chromium 3 of 3.
Thanks to xet7 !
After Clear emptied a refused range, WebKit kept a half-edited date
segment in the From field and fired `invalid` on it at the next submit -
though its validity read valid - so the form never submitted and the new
range never applied, with no message (card-date-range.e2e.js in WebKit,
also on retry: 0 submit events, `invalid` on .js-card-date-from). The old
Clear handler emptied the fields the same way, so this was there before.
Both range forms are novalidate now. The submit handler already decides
whether a range is valid (rangeRefused), marks the end date invalid with
the reason and says it in the form, so nothing is lost.
filterDateRangeRefusal.test.cjs pins novalidate on both forms. Against a
rebuilt bundle card-date-range and card-movement-filter pass twice each
in WebKit, Firefox and Chromium.
Thanks to xet7 !
Two causes behind most of EVERYTHING's WebKit failures so far, both in the
test pages rather than in WeKan:
- 94 specs switch language with setLanguage and then click at once. The
client loads that language afterwards and draws everything again, which
closes a popup opened before that: the board view popup of the flow and
report help specs (Latin monte-carlo in WebKit; Corsican and Scottish
Gaelic in earlier Firefox runs) had no entry left to click. The
fixture's context now wraps Meteor.callAsync in each page so that a
setLanguage call resolves once <html lang> - which client/lib/i18n.js
sets in the same flush that redraws - names the language. It waits at
most 10 s and then resolves anyway, so it can only make a spec patient,
never fail one. A page of a context a spec makes itself uses
untilLanguageShown().
- Playwright's WebKit freezes the page's main thread on
PushManager.getSubscription(): after it not even a timer runs. Member
Notification Settings asks it on opening, to show whether this device
gets push (client/lib/webPush.js), so every WebKit spec that opened it
hung in its next click - notification-activity-mute and the member
scope of notification-delivery-settings, in every language. In WebKit
the page now answers as a device with no subscription does. Chromium,
Firefox and Safari answer the real call; no spec subscribes.
WebKit: those notification specs 10 of 10, and the Latin, Corsican and
Product Backlog specs 13 of 13; Chromium and Firefox 11 of 11 each with
the same subset. playwrightPageInit.test.cjs runs both page scripts on a
fake page: setLanguage waits for the language and keeps its result,
other methods are not held, a page whose Meteor arrives later is
wrapped, the wait is capped; WebKit's getSubscription answers null and
Chromium's and Firefox's are left alone; the fixture installs both.
Thanks to xet7 !
Inside `each strokes` the template drew stroke-width=width, and Spacebars
looks a name up among the template's helpers before the line's own
fields: the width helper is the drawing's width, so every line was drawn
800 wide - one black shape over the whole whiteboard, in every browser.
The lines are mapped with strokeWidth now.
whiteboard.test.cjs: every name the line's path uses is a field of the
line and none is a helper of the template, which would win - it fails on
the code before. whiteboard.e2e.js checks each drawn path is the pen's
width (4) in both browsers.
The same spec drew the second person in another context of the same
browser. In Firefox two contexts share the pointer capture the first pen
takes: one line zig-zagged between both paths and the other page drew
nothing, 4 of 4. The second person now has a browser of their own, as
two people do; both pens are still down at once. Firefox 6 of 6 and
Chromium 4 of 4 against a rebuilt bundle.
Thanks to xet7 !
After a reload the notice for an unanswered keyboard undo or redo shows
as soon as its board is open - before the board and its members have
arrived. undoRedoLast does nothing while Utils.canModifyBoard() is false,
so a Retry clicked in that moment was dropped without a word and the
notice stayed (history-recovery-notice.e2e.js, Firefox).
Retry is disabled until the retry can act; Forget needs no right and
stays usable. For someone who may only read the board Retry stays
disabled, as it could never do anything for them.
historyRecoveryNotice.test.cjs pins the binding and the guard it mirrors,
and that Forget is not gated - it fails on the code before. The spec
waits for Retry to be enabled before it clicks; the spec passes in
Firefox against a rebuilt bundle.
Thanks to xet7 !
The filter sidebar's two date-range forms (card dates, card movement)
reported a reversed range with reportValidity(). Firefox opens that
bubble just under the end date - over the Apply and Clear buttons - and
while the field has focus it takes every click on them: no mousedown and
no click reached the page, twice in a row, so a refused range could not
be cleared at all (card-date-range.e2e.js, 4 of 4 in Firefox; with the
bubble suppressed it passed).
The end date is still marked invalid with the reason (setCustomValidity,
for :invalid and screen readers), and the reason is shown in the form as
a role="alert" line under the buttons. Typing in the range or Clear takes
it back.
filterDateRangeRefusal.test.cjs: both forms report through rangeRefused,
show the line and take it back on input and Clear, and nothing in the
filter sidebar calls reportValidity() - it fails on the code before. The
card-date-range and card-movement-filter specs check the line appears
with the reason, is gone after Clear or an edit, and is absent for an
accepted range; both pass in Firefox against a rebuilt bundle.
Thanks to xet7 !
viewport-matrix is Chromium-only (device emulation), so in Firefox and
WebKit every test of a viewport is skipped and its beforeAll never runs -
but its afterAll still does, and read board.boardId of a board that was
never seeded: each of the twelve viewports failed in EVERYTHING's Firefox
stage, on the last test of the group, in 0 ms.
afterAll returns when nothing was seeded. Firefox: 132 skipped, none
failed. Chromium: phone-portrait 11 of 11, and its board is cleaned up.
Thanks to xet7 !
The test switches language and then opens the board, and when the language
arrives the board header draws again and closes a popup opened before
that. In Firefox the click on the board view toggle often came first: no
popup at all, and no Product Backlog entry to open - 2 of 6 runs alone,
the st variant in EVERYTHING's Firefox stage.
The test opens the popup again until its Product Backlog entry is there.
9 of 9 in Firefox (st, en, haw, three times each).
Thanks to xet7 !
In Firefox a Khmer label overflows its Blocks block and widens the box the
test measures, so the first point of the block it found, scanning from the
left, was the block's outline - and a press there dragged nothing: km and
km-KH failed every time (4 of 4), every other language passed.
The grab point is now one with the block's own body on both sides of it.
The whole spec in Firefox: 104 of 104, Khmer, Georgian, Fijian,
Kinyarwanda (the long labels the search was written for) and Arabic
right-to-left among them.
Thanks to xet7 !
The member-scope test switches language just before it opens Notification
Settings, and when the language arrives the popup draws again with every
section closed. Under a whole-suite run in Firefox that came after the
click on the section's summary, so the text checks passed and the select
the test then used was hidden: vi and zh_SG failed, 101 others passed,
and vi passed 3 of 3 alone.
expand() opens the section when it is closed and waits until its content
shows, again if a redraw closes it. vi, zh_SG, en and fi twice each, and
the board-scope, webhook-form and tray tests: all pass.
Thanks to xet7 !
The list-sync specs answer as Jira or GitLab from a server in the test
process, and WeKan's server fetches from it. With the browsers in Docker
on macOS that server was on the container's loopback, which Docker
Desktop keeps from the host: "fetch failed", and every test of
list-sync-estimate, -planning, -source and -time-estimates failed in
EVERYTHING's Firefox stage.
build.sh already forwarded the test SMTP port from the host into the
container through docker exec; the same forwarder carries any TCP. On
macOS it now also forwards a small pool, 39101-39104 by default
(WEKAN_TEST_REVERSE_PORTS), and names it to the specs, which listen
through helpers/reachable-server.cjs: the first free port of the pool,
or any port when there is none, as before. Linux forwards nothing.
30 of 30 of those specs pass in Firefox in Docker now.
playwrightReachableServer.test.cjs: the pool is read strictly, a port in
use is skipped, no pool listens anywhere, and when every forwarded port
is taken it fails clearly instead of listening where WeKan cannot reach.
playwrightDockerHost.test.cjs: macOS forwards the SMTP port and the pool
(or the one configured), names it to the container and removes its ready
files; Linux forwards nothing.
Thanks to xet7 !
The test clicked Add dependency straight after the minicard. Under a
whole-suite run in Firefox that click landed while the card was still
opening, and the picker it opened was closed again when the card's route
and details settled - no popup, and the wait for its type select ran out
(the ve-PP variant; 59 of 60 passed, and it passed 3 of 3 alone).
The test waits for the opened card, then opens the picker again until its
type select shows. 6 of 6 (en, ve-PP, tlh, twice each) beside the suite.
Thanks to xet7 !
The opened card draws the sections above its checklist for a moment after
it opens, and they push the rows down: in Firefox they moved 50 px between
the test measuring them and releasing the mouse. With handles off the
release landed on item 1's own title and opened its editor, so the titles
read 2,3 (EVERYTHING's Firefox stage); with handles on the enabled drag
dropped item 1 between 2 and 3. 2 of 6 in a row failed that way.
drag() waits until two reads of the rows' positions 150 ms apart agree,
then measures and drags. 10 of 10, both variants five times, while the
whole suite ran beside it.
Thanks to xet7 !
The report is a snapshot taken when the search runs, and the live
one-second recovery worker leases each pending intent while it looks at
it. A search made during that lease shows the seeded orphan as "Delivery
reservation active" - correct - and the page keeps showing it, so the
wait for "Original activity is missing" ran out in EVERYTHING's Firefox
stage (the en variant).
The test searches again until a snapshot falls between leases, up to 30 s,
and then asserts the same status and the disabled Retry. 10 of 10 in
Firefox, every test twice, while the whole suite ran beside it.
Thanks to xet7 !
Even on the bridge network, EVERYTHING's Firefox stage lost a test now and
then to a page whose document never came (RuleBleed's cross-board test,
Red Strings, shared templates). Inside the container the cause was in
plain sight: a connect to host.docker.internal:3000 in SYN_SENT after nine
retransmits, while the next connection went straight through. Through the
container's proxy 9 of 1000 requests went unanswered; from the Mac itself
none did.
The proxy no longer waits on such a connect. It reads nothing from the
browser until a connection to the host is up (pauseOnConnect), drops a
dial that has not connected in 1.5 s - a normal one takes milliseconds -
and dials again, five times, so no byte is lost or sent twice. If none
connects the browser's connection is closed rather than left open.
playwrightDockerHostProxy.test.cjs: two unanswered dials, then one that
connects - the request arrives once and is answered, and the abandoned
dials are closed; when every dial is unanswered the client is closed after
exactly the attempts, in well under the old 30-60 s wait.
Thanks to xet7 !
43-mobile-allboards read the invitation tile's box, then each control's
box, one round trip at a time. Firefox draws the row above the board list
later than Chromium, and in between it moved the whole list down 29.4 px:
the Decline button (1102-1162) was compared with where the tile had been
(853-1139) and failed, 2 of 2, in EVERYTHING's Firefox stage. The tile was
never clipped - settled, it runs 882-1168 around all three controls.
The tile and its message and two buttons are measured in one layout now,
polled until they hold, and a missing or zero-height control fails too.
Firefox: 3 of 3. With the original #6488 bug put back (the tile forced to
4rem) it still fails, naming the message and both buttons.
Thanks to xet7 !
A spec and the server share files by path. admin-continuous-backup gives
the server a key file it wrote under .tools/tmp, and reads the stream.json
the server wrote to its target - and with the repository mounted at /repo
and the files root at /wekan-files, every such path named a file only one
side had: "The encryption key file cannot be read", and no stream.json, in
both of its tests in EVERYTHING's Firefox stage (the only browser there that
runs in Docker on this Mac, and on Linux arm64 every browser does).
Both are mounted at the path they have on the host now, the specs are told
that path in WEKAN_FILES_PATH, and the in-container script finds the proxy
and the Playwright binaries from its working directory. Both tests pass in
the container now.
playwrightDockerHost.test.cjs drives run_playwright_docker on macOS and
Linux: the repo and the files root are mounted at their own paths,
WEKAN_FILES_PATH and HOME follow, and the function names no container-only
path. It fails on the code before.
Thanks to xet7 !
Three Firefox tests of one EVERYTHING run waited the full 60 s in
waitForMeteor: their page's own document never arrived. Measured from
containers during that run, on Docker Desktop for macOS:
- host network, through the WeKan proxy: 3 of 600 requests lost
- host network, straight to host.docker.internal: 8 of 600 connects never
completed
- bridge network, through the same proxy: 0 of 1000
- from the Mac itself: 0 of 300
So it is Docker Desktop's host network, not WeKan and not the proxy. On
macOS nothing needs that network - WeKan and MongoDB are reached through
host.docker.internal and SMTP through docker exec - so the container runs
on the bridge network, where the proxy still serves localhost:3000 and the
browser keeps ROOT_URL's origin. Linux keeps the host network, which is
the real one there.
playwrightDockerHost.test.cjs drives run_playwright_docker with uname
stubbed: Darwin gets bridge and the proxy, Linux gets host and none, and
the function names no network of its own. It fails on the code before.
Thanks to xet7 !
The seven commits since the Dependabot entries: two bug fixes (continuous
backup watching before its first scan; single-file Mach-O signing and
start/backup waiting for WeKan) and five test-run fixes, grouped by topic.
The short changelog guard is a group of its own now, so the developer-
tooling subsection is grouped throughout. CHANGELOG.md regenerated.
Thanks to xet7 !
password-min-length-signup-login.e2e.js writes the password policy straight
into the settings document. The server learns it from its settings observer,
which polls on the standalone test MongoDB EVERYTHING runs: for up to ten
seconds it still answered the old policy. A sign-up form loaded meanwhile kept
the old minimum (6 instead of 10), and an administrator's Set password was
refused under the minimum the test before had left enforced - two failures in
EVERYTHING's Chromium stage that never showed on a dev server, whose MongoDB
is a replica set.
untilServerHas() asks the server (getPasswordPolicy) until it has the policy
the test wrote; the sign-up form is loaded again after it. 6 of 6 with two
workers, each test twice.
Thanks to xet7 !
"with every field allowed, the card draws exactly the Card pane's
sections" writes the board's Scrum visibility straight to the database and
reloads. The board publication's cursor has limit: 1, which Meteor's
change-stream driver does not follow, so it polls: a reload could still be
served the board as it was for up to ten seconds, and the card drew no
Scrum section - failing every time in EVERYTHING's Chromium stage. Live in
the browser the update arrived after 9.8 s; in the app itself a write
through a method re-polls at once, so only this direct write waited. The
test waits for the published board (up to 15 s) before it reads the card.
Thanks to xet7 !
button-theme-foreground.e2e.js opened Board Settings / Swimlane, List and
Card and asked each for its column-actions buttons. Card Settings has had
none since 3e5bc58105 ("Card Settings: a checkbox per section, ...":
card actions live in the card's own section hamburgers), so every theme
failed on Card, in EVERYTHING's Chromium stage. Swimlane and List are
checked, in all four themes, and the test says why Card is not.
Thanks to xet7 !
build.sh and build.bat started the test mongod on :3001 with its dbpath
in the checkout's parent directory (../mongodb-test-3001). That is the
one thing in a test run that was written outside the repository, against
the rule that everything WeKan's scripts write stays in .tools/ - and a
sandboxed editor, which may write only the repository, could not create
it, so EVERYTHING's browser stages had no database to run against. It is
.tools/mongodb-test-<port> now, which git and Meteor already ignore.
toolsDirOnly.test.cjs pins the new path in both scripts and fails on a
test database beside the checkout.
Thanks to xet7 !
The file engine scanned its sources first and started watching them
after, so a change made between the two - after the scan had passed a
file, before the watcher began - waited for the next scan, five minutes
by default. And macOS reports file events through FSEvents, which
delivers nothing for a moment after a watcher is created: an EVERYTHING
run on a loaded Mac lost a log line appended right after the start
(tests/integration/continuousBackup.test.cjs, "the appended log line was
streamed by watching", 4 !== 5).
The watchers start first now; a change seen by the scan and a watcher
too is recorded once, as they share one queue. On macOS one catch-up
scan runs two seconds after the start. Not elsewhere: inotify is
watching when fs.watch returns, and a second full scan of a large store
would double the start-up cost.
A new test, on macOS and Linux, with a watcher that never reports:
watching starts before the first scan; on macOS the catch-up scan
records a change made right after the start; on Linux no second scan
runs. It fails on the code before this change.
Thanks to xet7 !
When macOS refuses to signal a process group (EPERM, which it returns for
a group of zombies), the smoke test checked with `ps` whether any member
was still alive. A Mac that will not let the build run `ps` - "Operation
not permitted", as here, where EVERYTHING's node suites run - failed the
check itself, and with it a correct app: macAppPackaging.test.cjs, every
EVERYTHING run on this machine.
On macOS it asks the kernel through libproc now (proc_listpids for the
group, proc_pidinfo for each member: a zombie's info cannot be read, a
live one's status is not SZOMB), with no process started. Elsewhere it
uses ps exactly as before.
tests/mac-smoke-app.py: the ps cases run as the non-macOS path; on macOS
an EPERM asks libproc and never ps; and a real group - a live child, then
a zombie leader alone - is told apart. macAppPackaging.test.cjs passes,
three runs in a row.
Thanks to xet7 !
EVERYTHING's node suites run under Meteor's Node.js, which has single
executable applications, so singleFile.test.cjs built and ran a real
single executable - and it failed twice:
- The builder ad-hoc signed only a target named mac*/darwin*. Built from
this Mac's Node.js under another label (the test builds 'amd64'), the
file is still a Mach-O binary, the injected archive had invalidated its
signature, and macOS killed it (SIGKILL) before it printed anything:
status null. It signs every Mach-O output now, read from the file's
magic number.
- `start --background` and `backup` returned as soon as they had spawned
WeKan, before it wrote its pid file - which it does only after reading
its settings and extracting FerretDB. A `stop` right after them said
"WeKan was not running" and left it running. Both wait now
(status.waitUntilStarted) until the pid file names the process they
started, give up at once if it ended, and say so if it did not start.
singleFile.test.cjs: 24 of 24 under Meteor's Node.js, the real single
executable included; a new test pins waitUntilStarted, and that both
commands use it.
Thanks to xet7 !
changelogShort.test.cjs asserted an Upcoming section always, and one
Upcoming in CHANGELOG.md - but right after a release there is none until
the next real entry (CLAUDE.md, Making a release), so it failed after
every release, v12.77 included. With no Upcoming it checks that the
newest release is first and that nothing links an Upcoming that does
not exist; with one, what it checked before.
Thanks to xet7 !
Dependabot's 5.1.0 -> 5.2.0 bump (#6850) changed the app's package.json,
but a SAML login is verified by the wekan-accounts-saml package's own
copy, pinned by its Npm.depends to 5.1.0 - so nothing that runs changed.
5.2.0 is a hardening release a login relies on: it rejects a message
with more than one Issuer, checks idpIssuer on the Response and its
assertion, rejects a parsed query whose RelayState is not the signed
one, and checks a POST LogoutResponse's status and issuer.
The package pins 5.2.0 now. samlLogin.test.cjs required '5.1.0' in one
place and '^5.1.0' in the other; it requires the package and package.json
to name the SAME version, and at least 5.2.0, so a bump of one alone
fails.
A login roundtrip with the package's own copy (node-saml 5.2.0,
xml-crypto 6.4.0) and WeKan's options: a signed Response and a signed
Assertion log in; a NameID changed after signing, a signature by another
key, an unsigned response, and an answer to a request WeKan never made
are refused. saml-popup-error.e2e.js completes a real signed login in
the browser.
Thanks to xet7 !
The @aws-sdk/client-s3 3.1159.0 bump (#6844) resolved @smithy/core to
3.37.0, which npm marks deprecated: "a bug in custom logger data
redaction" (aws/aws-sdk-js-v3#8393). With a custom logger passed to an
AWS client, sensitive fields nested in a list or map reached the logger
unredacted. @smithy/core 3.24.0 to 3.38.0 have it, so 3.36.2 before the
bump did too; 3.38.1 is the fix.
WeKan's only AWS client is the S3 backup (server/methods/backup.js), and
it passes no logger, so nothing was logged. Updated all the same, in the
lockfile only and within the ranges the SDK already allows - one package,
nothing else moved. Its tarball matches the lockfile's integrity hash,
and compared with 3.37.0 it adds no network host, telemetry, process
spawning or install script. A multipart upload to a local S3 stand-in
still arrives signed AWS4-HMAC-SHA256, whole.
releases/check-telemetry.py --source failed on it: the deprecation text
links github.com, a URL origin new to package-lock.json. It passes now.
Thanks to xet7 !
Two node guards still described v12.76's behaviour before it changed on
purpose. calendarPopupResize asserted that a popup without a date editor
does not drag; every popup drags by its title bar now (#6839), and with
drag handles on only by its handle - both pinned instead, its mock no
longer answering every selector as if a drag handle were there.
calendarTodayMarker picked a month's days with filter(Boolean), which an
empty cell passes now that it is an object with its own _id (#6838); it
skips the cells marked empty.
Thanks to xet7 !
"Show rule button on minicard" is Board Settings / Card, Card buttons,
Show on Minicard (allowsCardButtonsOnMinicard), in WeKan since v12.16:
the board's card-button rules are drawn under each minicard, and a click
runs the rule on that card without opening it. Off by default, which is
why it was not seen. It had no test.
minicard-rule-button-6836.e2e.js (Chromium and WebKit): with it on, a
card-button rule's button is on the minicard and a click archives the
card without opening it; with it off, no minicard has one.
Thanks to mimZD and xet7 !
Fixes#6836,
Member Settings / Change Settings used the shared popup list as it is:
a flex row whose every line is 33px high with its icon centred. Its
settings are sentences, and most wrap - in Persian, in German - so a
setting became two tall lines with its icon floating between them and
the next setting right under it, with nothing to tell where one ended.
Each row is now an icon column, the words and the check mark at the
end; the icon sits on the first line, a wrapped line is an ordinary
line high, a rule divides the rows, and the Card settings checkbox has
the same space beside its words as an icon has.
Scoped to the popup's own list (change-settings-list): a popup keeps
the screens before it - the member menu - in the same .pop-over, so its
data-popup would have restyled those too.
change-settings-popup-6835.e2e.js, in Persian and English, Chromium and
WebKit: each row's icon is on its first line, a line is under 28px high
and a row only as tall as its lines, rows are divided, and the member
menu beside it is untouched.
Thanks to mimZD and xet7 !
Fixes#6835,
The dates row of a minicard reserved 84px at its trailing edge - a third
of a 250px card - for nothing: the avatars float after it, and clear:
both starts it below the menu. A due date with its "1 day left" was
broken over three lines beside that empty strip, in a Persian board of
the report. It reserves the drag handle's room now, 24px, and the same
badge takes two lines at the default list width.
The board header bar wrapping to a second row, and the list header icons
under the card count, in the same report, did not reproduce on the
current code at the same window width, in Persian, with the card count
on; they are in CHANGELOG TODO Later until the reporter can say which
WeKan and which browser zoom showed them.
minicard-dates-width-6834.e2e.js (Chromium and WebKit, English and
Persian): the dates row reserves at most 24px, and the due date badge is
at most two lines high.
Thanks to mimZD and xet7 !
WebKit puts the caret at the start of a field on focus and Chromium at
the end, so the d typed into the title landed at its start in WebKit and
the check for a title ending in dd failed there - while what the test is
for, that d typed in a field opens no Due date editor, held in both.
The test puts the caret at the end itself now.
Thanks to xet7 !
The opened card's title was split like a minicard's: the leading half
opened the editor and the trailing half dragged the window, so clicking
the right of the title, or beside a short last line, did not edit. Now
all of the title edits. With drag handles off, the window is moved from
the empty space of its header row above the title - between the collapse
caret and the menu; with drag handles on, only by the drag handle. A
reader who may not edit has no edit zone and still drags by the title.
Every popup now moves the same way - by its title bar with drag handles
off, by the handle before its title alone with them on - where only the
Drawing, Board View and Board Card settings popups and the date popups
moved before. A dragged popup is kept inside the window as it grows. The
drag pins a popup where it is before moving it: one placed from the
right edge (the member menu) jumped across the window, and one with a
margin by that margin, as the drag began.
card-title-edit-and-move-6839.e2e.js, in Chromium and WebKit: a two-line
title edits from its right, middle and left; the empty header row moves
the window by exactly the drag with handles off and not at all with them
on, when the handle does; and the card menu, Labels and the member menu
move by their title bar, or with handles on by the handle alone.
titleClickToEdit, popupMoveMaximize and popupBounds pin it.
Thanks to mimZD and xet7 !
Fixes#6839,
On a slower server a card's details render a few hundred milliseconds
after the click that opens it. `d` pressed in that moment found no Due
date control yet and did nothing - only the hovered-minicard path (#6755)
waited for it. Both paths now wait for THAT card's control, stop if
another card was opened meanwhile, click it once, and give up after 3
seconds.
The card details also closed any popup of their card when they were
destroyed (#6686), which a re-render of the card still open does too: a
popup could open and shut again at once. They close it now only when
the card is no longer the open one - closed, or another card opened.
The Playwright test emulates a 300 ms connection in Chromium, clicks
each card open and presses d at once: the editor opens and is still
open 1.5 s later, and closing the card still closes it. It fails on the
code before this change. dueDateKeyboardShortcut and
labelsPopupStaleCardContext pin both halves.
Thanks to mimZD and xet7 !
Fixes#6840,
The board Calendar view already has Today (and Calendar mode too), but
the date picker of every date popup - Due, Start, End, Received and the
rest - had only « ‹ › », so a user who paged to another month had to
page back. Today, between the month and ›, shows today's month and
focuses today; it changes nothing until a day is clicked and saved.
Testing it found a picker bug that was already there: going back to a
month drew it wrong - October came back as "29, empty, 30, empty", with
no 31st. Blaze matches list items by _id, and the weeks and days had
none; the empty cells, all alike, were moved around. Every week and
cell, empty ones too, now has an _id that names its month.
calendarPickerControls.test.cjs checks the ids, that Today shows today's
month and focuses today, and that it selects nothing; the Playwright test
pages two months ahead, presses Today, and checks every day of the month
in order, today focused, and the card's due date unchanged.
Thanks to mimZD and xet7 !
Fixes#6838,
The 18 new strings of Admin Panel / Problems / Password length and of the
"Change your password" dialog, written in the language and script each
locale tag names, with the terms each file already uses for password,
account, save and sign in, and __min__ kept exactly. Only those 18 keys
changed in each file, written with fill-translations.mjs --apply, which
fills English placeholders only.
Low confidence, for a speaker to correct on Transifex: ace, ak, an, ary,
ast-ES, ay, ba, bho, bi, bm, bo, br, bua, chr, co, csb, cv, cy, dz, ee,
ff, fj, fo, fur, ga, gd, gn, gv, ha, haw, hsb, ig, iu, jv, kl, kok, ks,
kw, lg, lld, mg, mi, mt, nah, nap, nd, nso, ny, om, pap, qu, rm, rn,
rup, rw, sah, sc, scn, sd, se, si, sm, sn, so, ss, st, szl, te-IN, tg,
ti, tig, tk_TM, tlh, tn, to, tpi, ts, tt, uz-AR, ve, ve-CC, ve-PP, vo,
wa, wa-RR, wal, wo, wuu-Hans, xh, yi, zgh.
Thanks to xet7 !
The title bar keeps its own copy of the menu labels, and the new pane
had none: tests/adminUrls.test.cjs failed on problems/password-length.
Thanks to xet7 !
PASSWORD_MIN_LENGTH (default 8) is now the length below which a sign-in
is REPORTED, in the new Admin Panel / Problems / Password length, and
PASSWORD_MIN_LENGTH_ENFORCE (off by default) is what makes it a rule: a
shorter new password is refused, and an account that signs in with an
older shorter one sees only "Change your password" until it has a long
enough one. Signing in is never refused, enforced or not, so nobody is
locked out by a minimum set after their password. LDAP, OAuth2, OIDC,
SAML, CAS and Sandstorm accounts are neither reported nor asked: their
password is their provider's. An administrator's Set password no longer
applies WeKan's policy to such an account.
Meteor sends a password hashed, so only the browser knows its length: it
notes it on the sign-in form and, after a sign-in that worked, says "it
was short" - never the password or its length.
The page has both settings and a table, one row per account: username,
first and last short-password login, the latest IPv4 and IPv6 address
with the country's flag and city when a CDN says where it is, the count,
and when it was fixed, with the Too short (default) / Fixed / All filter.
Fixed is a changed password not seen short since; a short one seen
again reopens the row.
Changing one's own password - Member Settings, the forced change, a
reset link - now signs EVERY session of the account out, the one that
changed it too, and only the new password signs in again. The browser
goes to the sign-in page as soon as it is signed out, since the server
closes the connection before the answer can arrive.
Documented in docs/Features/Admin-Panel/Problems/Password-length.md, the
settings on every platform. The new strings are in every locale in
English for now. Tests: passwordPolicy, passwordChangeRequired and
shortPasswordLogins (node); password-min-length-signup-login,
password-length-report and password-change-other-sessions (Playwright,
Chromium and WebKit), which change each setting on the page and check
what it does, and that sign-up with no mail server registers and signs
the user in.
Thanks to xet7 !
Registering answered "Internal server error" - and created the account
anyway, so the next try said it already existed - and the
starredPublicBoards and translation publications failed for everybody:
"Did not check() all arguments". audit-argument-checks asks,
synchronously, the moment a handler returns, whether check() saw every
argument. The REQUIRE_2FA wrapper of every method and publication, and
the Audit log's wrapper of the administrators' methods, were async and
awaited before they called the handler - even with REQUIRE_2FA off - so
the handler's own check() came after the audit had already failed the
call, and the handler went on running.
A wrapper now calls the handler at once when it needs no lookup
(REQUIRE_2FA off, nobody signed in, a cached answer). When it must await
first, it hands the audit over (server/lib/argumentChecks.js): the
arguments count as seen for the outer audit, and the handler runs under
an audit of its own that it must satisfy exactly as unwrapped.
tests/wrappedHandlerArgumentChecks.test.cjs runs both wrappers under a
model of the check package's audit: a checking handler passes, off and
on, a refusal stays a refusal, a handler that does not check still fails
on every path, and no wrapper in server/ awaits before it has checked.
Thanks to xet7 !
A build from the menu printed uname -p and sysctl hw.cpu64bit_capable
over and over and never reached its first package. With
METEOR_WAREHOUSE_DIR set and no SANDBOX, Meteor takes the running tool
to be wherever the warehouse's "meteor" link points - 3.5.4, the release
it was installed with - so with .meteor/release at 3.6-rc.1 every start
springboarded to 3.6.0-rc.1, found itself the wrong tool again and
springboarded again: 28 times in 20 seconds of meteor --version. The
earlier 1.1 GB build log was the same loop, its module trace printed
anew on every exec.
tools-env.sh and tools-env.bat now set SANDBOX=true beside the
warehouse, as Meteor's own self-test does; that one check is all it
changes. The Nextcloud test scripts and the dev-source runner, which set
the warehouse themselves, do the same. meteor --version now springboards
once, from the linked 3.5.4 to 3.6.0-rc.1.
tests/meteorSpringboardLoop.test.cjs checks both env scripts, that
nothing sets the warehouse without SANDBOX, and runs the installed
Meteor to count its springboards.
Thanks to xet7 !
Turning the NODE_DEBUG=module trace off for EVERYTHING alone was not
enough: a dev build from the build.sh menu wrote 1.1 GB, three million
lines, in its first three minutes, before it was interrupted. The trace
is now opt-in in build.sh and build.bat alike, and the diagnostics line
says whether it is on and how to turn it on. The other build diagnostics
(resolver commands, npm verbose, METEOR_PROFILE) are unchanged.
Thanks to xet7 !
The build's NODE_DEBUG=module trace wrote over 12 GB in the first half
hour of an EVERYTHING run on macOS - twice, through tee - and slowed the
build it traced, so the run would fill the disk before reaching the tests.
A build from the menu still traces by default; WEKAN_BUILD_TRACE_MODULES=0
turns it off, and EVERYTHING sets that unless the caller asked for 1. The
other build diagnostics (resolver commands, npm verbose, METEOR_PROFILE)
are unchanged.
Thanks to xet7 !
CodeQL #599 (js/insufficient-password-hash, CWE-916) flagged the SHA-1
the server computed for PASSWORD_CHECK_PWNED. That digest is not password
storage - accounts-password keeps passwords with bcrypt - and it is never
stored or logged: Have I Been Pwned's range API is keyed by SHA-1, its
first 5 hex digits are the question and the rest is compared in memory.
A slow hash cannot be used, because the service could not match it.
The browser and the server now compute it with one implementation,
breachLookupDigest in models/lib/passwordPolicy.js, which says what it is
and is not; the server no longer hashes a password with a hash of its
own, and a digest that cannot be computed lets the password through.
Tests pin that the digest is SHA-1 for ASCII and UTF-8, that it reaches
only the lookup and the comparison, and that nothing in WeKan hashes a
password-named value with a fast hash. Unreleased hardening, so no Hall
of Fame entry: docs/Security/Password-Breach-Check-Digest-2026-10-31.md.
Thanks to GitHub CodeQL and xet7 !
The 16 strings the security features added were English placeholders in
every locale; each is now in the language and script its locale tag
names, with the terms the file already uses for password, account,
attachment, backup and two-factor authentication. __min__ is kept
character for character. Where the right word is spelled like the
English ("Administrator"), the column header says "Admin" or "Who"
instead, because a value equal to English is not a translation.
fy and fy-NL: the existing two-factor strings were Dutch in a Frisian
file, and are Frisian now; tests/frisianTwoFactorLanguage.test.cjs.
tig: Tigre spells storage መክዘን, not Tigrinya's መኽዘን.
Low confidence, for a speaker to correct on Transifex: ace, ak, an, ary,
as, ast-ES, ay, ba, bho, bi, bm, bo, br, bua, chr, ckb, co, csb, cv, dz,
ee, ff, fj, fy, gn, gv, haw, hsb, ht, ig, iu, kl, kok, ks, kw, la, lg,
lld, mg, mi, nah, nd, nso, ny, om, pap, ps, qu, rm, rn, rup, rw, sah, sc,
scn, sd, se, si, sm, sn, so, ss, st, szl, tig, tlh, tn, to, tpi, ts,
uz-AR, ve, ve-CC, ve-PP, vo, wa, wa-RR, wal, wo, xh, yi, zgh.
Thanks to xet7 !
/admin/people/audit-log opens the pane and names it in the title bar like
every other People pane, and the table aligns to the start of the line,
so it reads right to left in Arabic, Hebrew, Persian and the rest.
Thanks to xet7 !
Admin Panel / Attachments / S3 / Server-side encryption: SSE-S3 (AES256)
or SSE-KMS (aws:kms, with a KMS key or the account's default). S3
encrypts each object as it is written and decrypts it for WeKan on read,
so reads, range requests and moves are unchanged. Every attachment and
avatar upload, every renamed object and every backup written to S3 now
carries the choice; a value that is not one of them, or a malformed key,
adds nothing.
On the filesystem, encryption at rest stays with the disk or volume
(LUKS, an encrypted cloud volume): exports, PDF, thumbnails, virus scans,
backups and WebDAV all read the stored files directly, and encrypting
them inside WeKan would change every one of those and convert every file
already stored.
The new interface strings are pending translation.
Thanks to xet7 !
Nothing recorded who set somebody's password, made somebody an admin,
changed a login setting or restored a backup. Every call of those
administrative methods by a site administrator is now kept, one document
each, for a year: when, who, which action, the outcome - a refused call
too - its arguments and the address. A password or a secret never
reaches the trail: values under a credential-named key, the arguments
that are a password by position, and a password inside a URL are
replaced by [redacted]. An administrator editing cards is not in it,
the trail is shown to site administrators only, and a trail that cannot
be written never changes what the method did. It and the REQUIRE_2FA
wrapper keep each other's marks, so neither wraps the other twice.
The new interface strings are pending translation.
Thanks to xet7 !
LOGOUT_WITH_TIMER ends a login at a deadline however busy its user is;
nothing ended a session somebody walked away from. LOGOUT_IDLE_MINUTES
(Admin Panel / People / Login, every platform; empty or 0 is off) does:
the browser reports a key press, click, touch or scroll at most once a
minute, the server keeps the time per login token in idleSessions, and
once a minute every token idle past the limit is removed, which signs its
connections out. A tab that reconnects and resumes its login by itself -
a laptop waking up - is not activity and does not extend the session.
REST API tokens have no session record and are never removed. The time
is kept beside the user rather than on the token, because updating one
element of the token array needs the positional $ operator, which not
every database WeKan runs on supports.
Thanks to xet7 !
New passwords: PASSWORD_MIN_LENGTH (default 8) and PASSWORD_CHECK_PWNED
(off by default), in Admin Panel / People / Login and on every platform.
The browser sends a password to Meteor hashed, so the server checks every
password that reaches it as text - Admin Panel Set password and Add user,
POST /users/register - and the sign-up, Change Password, reset and
enrolment forms check in the browser. Signing in is never checked, so an
existing shorter password keeps working until it is changed. A breached
password is found with Have I Been Pwned's k-anonymity range API: only 5
hex digits of the SHA-1 leave WeKan, and an unreachable service never
refuses a password.
REQUIRE_2FA: off (default), admins or all. It covers accounts that sign in
with a WeKan password; LDAP, OAuth2, SAML, CAS and Sandstorm accounts sign
in through an identity provider and are not asked. Such an account can
still sign in, sees only the 2FA set-up until it has 2FA, and until then
the server refuses every method and publication but signing in and
enrolling, HTTP routes do not accept its login and the REST API gives it
no token. Once required, 2FA cannot be switched off.
The new interface strings are pending translation.
Thanks to xet7 !
POST /import-trello and other HTTP routes accept the login cookie as the
login, so a page on another site could make a signed-in browser send one.
The cookie is SameSite=Strict, which keeps it off such a request in
today's browsers; this is the second line, for older browsers, same-site
sibling hosts and routes added later. A POST, PUT, PATCH or DELETE whose
only login is the cookie must carry an Origin, or Referer, naming
ROOT_URL's host or the Host / X-Forwarded-Host it was sent to, which on
Sandstorm is the grain's own host. Token-authenticated API calls, Meteor's
cookie routes and DDP are not affected. A refusal is recorded in Admin
Panel / Problems as csrf.cross-site-cookie and never blocks the account
whose cookie it was.
Thanks to xet7 !
WeKan sent no HSTS header, so a link typed as plain HTTP or a downgrade on
a hostile network could strip TLS from the next visit unless the reverse
proxy added it. It is now sent whenever ROOT_URL is https: 180 days, this
host only. HSTS_MAX_AGE, HSTS_INCLUDE_SUBDOMAINS and HSTS_PRELOAD (Admin
Panel / People / Login, every platform) change it; 0 stops it, and preload
is sent only with includeSubDomains and at least a year. Every response
also carries X-Content-Type-Options: nosniff and
Referrer-Policy: strict-origin-when-cross-origin.
Thanks to xet7 !
snapd keeps the snaps in /var/lib/snapd/snap on Fedora, RHEL, AlmaLinux,
Rocky Linux, CentOS Stream and Arch Linux, which have no /snap, so the
instructions that exported /snap/wekan/current/bin and its lib directory
did not find mongodump and mongorestore there. They now pick the directory
with one S= line and the CPU's library directory with $(uname -m), and the
scripts that can be run from a host shell find the snap in either place.
Thanks to Group-Deed and xet7 !
Boards, swimlanes, lists and cards from before the templates feature have
no `type`: the schemas' defaultValue fills it on insert only. All Boards
publishes `type: 'board'` and the card queries ask for 'cardType-card', so
a Sandstorm grain migrated from MongoDB 3 opened onto an empty All Boards
page although its board was intact in the database. The new
type-defaults-backfill startup step sets the schema default on every
document whose type is missing, null or empty, and never touches an
explicit type.
Thanks to xet7 !
A report described "failure to invalidate session on password change": the
same account signed in on two browsers, the password changed in one, and
the other's session "updated" rather than ended. WeKan does not do that.
Member Settings / Change Password is Meteor's changePassword, which removes
every login token but the changing session's; the admin's Set password is
setPasswordAsync, which removes them all, as resetPassword does; and a
session whose token is gone is closed, while WeKan's HTTP routes accept only
a token still in the list.
password-change-other-sessions.e2e.js runs the report in two browser
contexts: both signed in with the password, the password changed through
the form in one, and the other is signed out within seconds, stays signed
out after a reload, and only the new password signs in. It passes three
times in a row in Chromium and in WebKit. passwordChangeSessions.test.cjs
pins those paths and fails on a password change with logout: false, the
LDAP sign-in's password sync being the one stated exception.
Thanks to xet7 !
CVE-2026-98432 is a Linux kernel use-after-free in IPv4 TCP connection
handling, shown to give remote code execution through an MPTCP listener
(golang-announce, 2026-10). Since Go 1.24 every Linux listen socket of a
Go program is MPTCP by default, and WeKan ships two Go servers: Caddy, the
snap's web server on ports 80 and 443, and FerretDB, the default database.
Every place WeKan starts one sets GODEBUG=multipathtcp=0: the snap's
environment (every app, Caddy and FerretDB among them), the Docker
entrypoint and the bundle launcher - which keep an operator's own
multipathtcp setting - every docker-compose FerretDB service (the commented
FerretDB 2 example too), and the FerretDB the Sandstorm grain starts. Caddy
is an upstream binary, so no rebuild is needed: Go reads GODEBUG at start.
wekan/FerretDB builds with `godebug multipathtcp=0` as well. The real fix
is a patched host kernel.
Checked on Linux: a Go server's default listener reports MPTCP true;
GODEBUG=multipathtcp=0, or the go.mod setting, makes it false.
tests/goMptcpOff.test.cjs pins every launcher, runs the launcher line, and
fails when something turns MPTCP back on.
Thanks to xet7 !
"Prepare v12.73 release" renamed Upcoming, but its push was refused and
the release workflow was never dispatched: there is no v12.73 tag and no
v12.73 GitHub Release. As CLAUDE.md says for a release that published
nothing, the heading goes back to "# Upcoming WeKan ® release", so the next
release-all.sh takes v12.73 again. It also gains the entry for the fix that
keeps this from happening again.
Thanks to xet7 !
v12.73 was prepared on a main that origin had moved past: the release
bot's v12.72 version bump had landed meanwhile. release-all.sh renamed
Upcoming and committed "Prepare v12.73 release", its bare git push was
refused, and set -e stopped it before gh workflow run - so no release ran,
and no tag was made, while the CHANGELOG already said v12.73.
release-all.sh now runs build.sh's git pull right after its checks, before
it chooses the version or renames anything, and checks the Upcoming section
again after it; and it pushes with build.sh's git push, which repairs moved
CHANGELOG links and pulls and pushes once more when origin moved meanwhile.
If the push still does not finish, it says that the workflow was NOT
dispatched, and the command to dispatch it.
releaseUpcomingPreflight pins the order - the pull before the version and
the rename, the push before the dispatch - and fails on a bare git push;
releaseTelemetry and the version-selection fixture follow.
Thanks to xet7 !
CodeQL alert #598 (incomplete URL substring sanitization, CWE-20) flagged
tests/sandstormOrgHosts.test.cjs, which skipped files that did not include
the old Sandstorm host name as a substring. The file no longer names that
host at all. It finds every Sandstorm host on a top-level domain other than
org by its shape, written as a URL - a scheme or // before it, or a port or
path after it - so property paths such as services.sandstorm.id, file names
such as sandstorm.js and the domains reserved for tests are not hosts. The
demo and the store workflow's older-market publish step stay allowed by
their subdomain and place.
A new self-test checks what it finds and what it does not; run against the
docs and workflows from before the sandstorm.org switch it finds 13 old
hosts, the installer address of release-all.yml among them.
Thanks to xet7 !
CHANGELOG.md showed only the five newest releases, so what was about to be
released was only in CHANGELOG-big.md. changelog-short.mjs now puts the
unreleased # Upcoming WeKan ® release first - its In short and a link to
its full section - and the five newest releases below it; right after a
release, when there is no Upcoming yet, none is invented.
CLAUDE.md and AGENTS.md say what "update wekan changelog" means: find the
commits not in the changelog yet, add an Upcoming section when a release
was cut meanwhile, write the entries and the In short into
CHANGELOG-big.md, run changelog-short.mjs, run the changelog tests, and
commit both files.
tests/changelogShort.test.cjs: Upcoming first, whole, above the releases,
its link resolves, and no Upcoming appears after a release.
Thanks to xet7 !
CHANGELOG-big.md is the changelog as it was: # Status, # Upcoming and every
release in full, with its <details> entries. Everything that writes or edits
the changelog uses it - release-all.sh, release-notes.sh and the release
workflow, the commit-link repair in build.sh and fix-changelog-hashes.sh,
the archive, the wrapper, the version scripts - and the release notes'
"More details at ChangeLog" link goes to its section. remote-release.py,
shared with FerretDB and the patch repositories, takes the file from its
config, and only WeKan's names CHANGELOG-big.md.
CHANGELOG.md is new: no Status and no Upcoming, only the In short paragraph
of each of the five newest releases and a link to its full section, in plain
Markdown - no <details>, <summary>, </summary> or </details>, and HTML links
written as Markdown links. releases/changelog-short.mjs writes it;
release-all.sh runs it right after naming the release, so it is in the
release commit, and changelog.sh does too. The Sandstorm App Market keeps
embedding CHANGELOG.md, now the short one.
old-CHANGELOG/'s headers say they moved out of CHANGELOG-big.md. References
to Upcoming or TODO Later in code comments and docs name CHANGELOG-big.md.
CLAUDE.md and AGENTS.md describe both files: CHANGELOG-big.md is written,
CHANGELOG.md is generated and never edited by hand.
tests/changelogShort.test.cjs: CHANGELOG.md is exactly what the generator
writes, the five newest releases with their whole In short, links that
resolve, and none of the four tags or an HTML link; the changelog tests
read CHANGELOG-big.md.
Thanks to xet7 !
Only the rename, in a commit of its own, so that git log --follow and blame
keep the changelog's history; the next commit adds the new short
CHANGELOG.md and points the release tooling at CHANGELOG-big.md.
Thanks to xet7 !
#6833 publishes WeKan to both Sandstorm app markets, apps.sandstorm.org
and the older apps.sandstorm.io, whose API is alpha-api.sandstorm.io.
sandstormOrgHosts.test.cjs refused every sandstorm.io host; it allows
that publish step in sandstorm-store.yml only, and checks that both
markets are published to.
Thanks to ocdtrekkie and xet7 !
Checked against the 2024 Confusion Attacks on Apache httpd
(blog.orange.tw, CVE-2024-38472 to -38477 and CVE-2024-39573, fixed in
2.4.60). The guide's examples were reverse proxies only, with no
RewriteRule, but each passed the WebSocket with ProxyPassMatch and $1 - a
request capture in a proxy target, the shape those attacks abuse - and in
the sub-URL example that rule looked for ^/sockjs while the requests come
to /wekan/sockjs, so its WebSocket never connected.
- Both examples pass the WebSocket with "ProxyPass ... upgrade=websocket"
on their fixed prefix (2.4.47+); no ProxyPassMatch remains.
- The guide asks for httpd 2.4.60 or newer, drops NameVirtualHost (a no-op
in 2.4) and the path in the sub-URL ServerName, which is not valid.
- A "What not to add" section: no RewriteRule or ProxyPassMatch with a
request capture in the target, no <Files>/<Location> ACL in front of
WeKan, AllowEncodedSlashes Off and MergeSlashes On, <Directory /> denied.
Verified with Apache 2.4.67: both virtual hosts parse, and in front of a
WebSocket server both pass HTTP and the WebSocket, at / and at /wekan;
the old sub-URL example fails its WebSocket ("Unexpected server response:
200"). tests/apacheReverseProxy.test.cjs pins it and fails on the old guide.
WeKan's own redirects stay on its origin (CAS checks the callback's origin
against ROOT_URL; the file and avatar redirects build their own paths), so
a backend-chosen Location cannot be steered elsewhere.
Thanks to xet7 !
Custom URL schemes in card text link only when Admin Panel -> Features ->
URL lists them, and never javascript:, data: or another scheme that runs
code in the page. A listed operating-system handler was linked, though, and
some have a public code-execution history: ms-msdt (Follina,
CVE-2022-30190), search-ms and search, ms-officecmd, ms-appinstaller,
ms-cxh and ms-cxh-full, and telnet - the last step of the 2026 Edge full
chain, where Windows ran telnet.exe from a user-writable PATH directory.
An administrator listing one would let any board member plant a link that
runs something on whoever clicks it.
- They are never linked, listed or not, by the app's parser and by the
markdown package's copy of the rule (the two lists must be identical),
and a click on one does nothing should such a link reach the page.
- A card title or description, comment, checklist or checklist item that
ADDS a link to one is shown in Admin Panel -> Problems (HandlerLink,
CWE-939), with who wrote it; the text is saved as written and the writer
is told nothing. The word in prose, or a link the text already had, is
not recorded.
- docs/Features/Admin-Panel/Settings/Visibility.md lists both kinds of
never-linked scheme, and says what a listed file: does on Windows (an SMB
link outside the network sends the clicker's NTLM hash).
Tests: tests/urlSchemeAllowlist.test.cjs (positive and negative, through
the real DOMPurify, both sanitizer passes and the detector; fails on the
old code), and custom-url-schemes.e2e.js, which passes in Chromium and
WebKit: listed handlers do not link, a description saved with one through
the editor is recorded for its writer, and a kept link or a comment that
only mentions the word is not. Firefox does not start in this
environment.
Thanks to xet7 !
The v12.70 release renamed Upcoming to v12.70 at 23:59, and the entries for
the Hall of Fame guards (43c4d41877) and the Sandstorm install address
(ca2dc5fae5), committed after it, were added to its Tests and Releases
groups - inside a released section, for commits that release does not
contain, and with no Upcoming section left for release-all.sh to take the
next version from. They move to a new Upcoming section; the v12.70 section is
again exactly what v12.70 shipped. changelogEntriesBelongToTheirRelease
passes.
Thanks to xet7 !
v12.69's build-sandstorm in release-all.yml stopped because the
certificate of install.sandstorm.io had expired. It fetches the installer
from install.sandstorm.org, as sandstorm.yml and sandstorm-store.yml
already did, and so does releases/install-sandstorm.sh. That installer
downloads Sandstorm from dl.sandstorm.org and configures a development
server at local.sandstorm.org, so the docs follow: docs, apps, dl, install
and local.sandstorm.io links and addresses become sandstorm.org in the docs,
SECURITY.md, code comments and the accounts-sandstorm README, each URL
checked to answer there. demo.sandstorm.io stays, as it has no sandstorm.org
name; released CHANGELOG sections are records and stay as they are. The
release audit's baseline allows the two code comments' docs.sandstorm.org.
tests/sandstormOrgHosts.test.cjs: every Sandstorm build installs from
install.sandstorm.org, and no tracked file names a sandstorm.io host
outside those kept on purpose; it fails on the old release-all.yml.
Thanks to xet7 !
hallOfFameProblemsCoverage and securityRegressionCoverage collected the
names from <td valign="top"><b>Name</b></td>, so a name that was a link to
its page was never seen. Now that every name is one, the match takes the
link; the four it had missed - ImportPlacementBleed, SamlSubjectBleed,
StampBleed and UploadBleed - are listed as guarded by the suites that name
them, and the inventory count goes from 137 to 141.
Thanks to xet7 !
v12.69's build-sandstorm stopped because install.sandstorm.io's certificate
expired that day (Let's Encrypt, notAfter 2026-10-29 19:02 GMT), and
fetch.sh reported "The server is having an outage" for curl's exit 60. It
now says the certificate could not be verified and that only that server's
owner can fix it. dl.sandstorm.io, where Sandstorm itself is downloaded
from, is valid.
Thanks to xet7 !
releases/prepare-launchpad-source.sh uploads only the files the snap recipe
reads, and listed releases/fetch.sh without releases/tools-env.sh, which
fetch.sh sources for its TMPDIR. In v12.69's armhf build the caddy part's
fetch.sh had no TMPDIR, mktemp made nothing, and curl got an empty -o seven
times ("option -o: is badly used here"), with pauses of up to 480s, before
it was reported as a server outage.
- tools-env.sh is uploaded, and it makes .tools/tmp in a tree that has only
snapcraft.yaml and releases/, as the Launchpad source does.
- fetch.sh stops at once when it has no temporary file, and does not retry
curl's exit 2 (it could not start) or call it an outage.
- tests: every releases/ file a listed script sources or runs must be
listed; tools-env.sh and fetch.sh work in such a tree with no .git and no
TMPDIR; both fail on the old list. fetch.sh's two new refusals are tested.
s390x failed earlier, on Launchpad: even a new build answered "Could not
find repository at path" three times.
Thanks to xet7 !
releases/fetch.sh downloaded into a mktemp file and moved it into place, so
every download kept mktemp's 0600, and the bundled ferretdb and mongo-tools
were 0711 after chmod +x. A user other than the owner gets "Permission
denied" from such a file, and since today's appimagetool checks for it, all
six v12.69 AppImages (i686, x86_64, aarch64, armhf and both musl ones)
failed: "has mode 0711, but it must be readable for all users".
A download now gets the mode its umask gives a new file, and the AppDir is
made a+rX before it is packed. tests/releaseDownloads.test.cjs fails on the
old fetch.sh with 711 instead of 755 and checks that a stricter umask is
still followed; tests/appImageRuntime.test.cjs pins both.
Thanks to xet7 !
Since Board Settings / Card became the card's Card Settings (52de57c844),
the Card Draggable table is in the Card section's hamburger, not in the
popup, so board-drag-settings.e2e.js failed in every language waiting for a
table that is no longer there. It opens Card the way the cog menu does and
then that hamburger, as board-settings-columns.e2e.js already does.
Thanks to xet7 !
F02 F06 reset three cards to sort 0 in the database and waited only for the
minicards to show in id order. gamma 0, alpha .125, beta .25 already is id
order one time in six, so the wait passed while the client still had the old
sorts, the move was computed from them, and the card landed last instead of
second (1 in 3 runs here). The test waits for the client's own copy of all
three cards to read sort 0; 8 repeats pass.
Thanks to xet7 !
ReplyBleed is one Problems row per source, and the webhook's source is the
client address, so the bounce and board-address tests leave rows of their
own in the test database, which persists between runs. The test's findOne
picked one of those (source "ses"), whose count the spoofed reply never
moves, and failed although the attempt was recorded under 127.0.0.1. It sums
the count over all ReplyBleed rows, the way emailBounces.tests.js already
does.
Thanks to xet7 !
mac-smoke-app.py checked that a killed descendant was gone with ps, which a
sandboxed macOS app may not run; it waits for os.kill(pid, 0) to find the
process gone and uses ps only to tell a zombie from a live one. macosSandbox
skips its live sandbox-exec check only when even an (allow default) profile
cannot be applied, which is the case inside the sandbox it is testing.
Thanks to xet7 !
Whether a Launchpad build log belonged to the current attempt was decided by
[ "$log" -nt "$attempt_marker" ] alone. bash 3.2 compares whole seconds, so a
log written in the marker's second was not "newer": it was neither printed
nor checked for an infrastructure failure. this_attempts_log() counts a log
whose name did not exist when the attempt started, or one rewritten after the
marker; an earlier attempt's untouched log still does not count. Used by
infra_failure() and by the log printing, and tested for all three cases.
Thanks to xet7 !
hashlib.file_digest is Python 3.11+ and Path.write_text(newline=) is 3.10+,
so releases/bun/assets.py failed with AttributeError/TypeError on macOS's
python3 3.9. It hashes in chunks and writes the LF-only .sha256sum through
open(newline='\n'), and the test still pins LF on every platform.
Thanks to xet7 !
In a sandboxed macOS app ps fails with "operation not permitted", so every
live conformance run looked dead and a second run would remove its
containers. command_line_of() falls back to pgrep -lf, which still works
there. The test pins the fallback.
Thanks to xet7 !
A Node.js linked to OpenSSL 4 reports an AES-GCM tag mismatch as
ERR_OSSL_BAD_DECRYPT ("bad decrypt") instead of OpenSSL 3's "Unsupported
state or unable to authenticate data", so decryptFile() let the raw OpenSSL
error through instead of "An encrypted backup file has changed or the key is
wrong". Both are recognised now; tests/continuousBackupEncryption.test.cjs
passes with OpenSSL 4.1.0 and 3.5.9.
Thanks to xet7 !
A researcher who sent a report to security@wekan.fi, or some other way, and
is later able to add it as a GitHub security advisory: the maintainer
requests the CVE through that GHSA, and the Hall of Fame row's 3 stars
become 5. SECURITY.md says so to researchers. CLAUDE.md and AGENTS.md tell
an AI updating or auditing the Hall of Fame to check each such 3-star row
against the advisories, match by the report's subject and reporter, and
upgrade the index row, its own page, the CVE cell, the Process cell and the
star audit together.
Thanks to xet7 !
The scoped importer, reached by POST /api/import/zip and the importScoped
method, authorized the import on boardId alone. The listId and swimlaneId
beside it were written onto the new cards as given, and a cardId was looked
up with no board filter, so a writer of board A could create cards on A
whose list and swimlane were board B's, and could probe whether a card id
existed on a board they cannot see.
Before anything is written, the list, swimlane and card are now looked up
together with the board's id; an id of another board is refused (400 over
REST, error-import-placement from the method), with the same answer for a
foreign card and a missing one. Cards are placed only by what was found on
the board. A refusal is recorded in Admin Panel / Problems as
ImportPlacementBleed (authz.import-placement), without blocking the account.
Tests: the decision with the reporter's ids, the check before every write,
a negative scan for a target id written or looked up without the board, and
a browser test of the reporter's attack by both routes, which fails on the
previous code.
Thanks to Zachariah Mithani (AploSec) and xet7 !
The planner needed recover, so one job failing again in the rerun of the
full release skipped the planner and every build after it, and assets that
could be filled were not. For v12.68 the rerun failed on four Launchpad
snaps (503 / timeout) and on the OpenBSD bundle, whose start-wekan.sh fault
is in the tagged code and so in every rerun of it. The planner now runs
unless the run is cancelled, and each job after it checks the planner's own
result, since an implicit success() also counts recover. recover stays red
and now names the jobs that failed again.
Thanks to xet7 !
Meteor 3's server Accounts.createUser is createUserAsync. inviteUserToBoard
called it without await, so the new user's id was a Promise; findOneAsync
read the Promise as the empty selector {} and returned the FIRST user,
usually the site administrator. That user was added to the board and got
the invitee's language and invitedBoards, while the invitee was created but
not added. It was not FerretDB: any database did the same.
createUser and sendEnrollmentEmail are awaited (an enrollment email that
fails is logged, as the invitation email is), and the user found must be
the one just created. Every server ReactiveCache getter now refuses a
Promise as its id or selector, so a missing await fails loudly instead of
picking the first document. Tests: the guard and the reporter's case in
memory, a scan that no server file calls an async Accounts API without
await, and a browser test that inviting a new address adds exactly that new
account - it fails on the previous code.
Thanks to momosari and xet7 !
Fixes#6831.
A History row of a date-valued custom field held datePaths as an array of
path arrays, [["3","value"]]. FerretDB v1 does not store an array inside an
array, so on FerretDB the change was not recorded, and migrateTextDatabase
to FerretDB dropped those rows while it swallowed the per-document error and
still reported success.
Each path is now a JSON string, '["3","value"]'. Rows already written in
the array form on MongoDB still restore, and the migration to FerretDB
rewrites them to strings. The migration counts every document the target
refuses and ends as failed with the count per collection and the first
error, instead of success: true.
Thanks to vijlautens and xet7 !
Fixes#6832.
OpenBSD's sysctl prints nothing and exits 0 for a name it does not have, so
`sysctl -n hw.memsize || sysctl -n hw.physmem` stopped at the empty
hw.memsize and $(( / 1048576 )) ended start-wekan.sh before WeKan answered.
Both start-wekan.sh copies now try hw.memsize, hw.physmem64 (NetBSD, whose
hw.physmem is 32-bit) and hw.physmem in turn and take the first numeric one.
The test runs the lines against fake OpenBSD, NetBSD and macOS sysctls, and
shows the old chain failing on the OpenBSD one.
Thanks to xet7 !
The opened card and the minicard now draw Card Settings' sections, in
Card Settings' order, each section's fields in the order of its rows:
- Section order is per side: cardSettingsSectionOrder for the card,
the new minicardSettingsSectionOrder for the minicard. A board that
stores none: the card starts with Labels, Date, Members, Card, ...
(the card's historical order as far as the grouping allows), the
minicard with what it drew first before.
- CARD_LAYOUT's and MINICARD_LAYOUT's reorderable sections are Card
Settings' sections, so dragging a section or a row in Card Settings
moves it on the card or the minicard. Stored field orders map: a
field keeps its place relative to the others of its new section.
- On the card every section has the shared heading (its icon, title,
caret and hamburger) - Voting and Planning Poker too, which had a
heading of their own. The minicard stays compact: no headings.
Card Settings shows both sides at once: the minicard's pane first (on
the left), the card's second, each with a caret that folds it, each
scrolling on its own, stacked when the popup is too narrow for two.
The Show radio is gone. A splitter between the panes changes their
widths by dragging, like spreadsheet columns (clamped, kept per user
as a fraction, reset by a double-click, moved by the arrow keys). The
popup is wider again for two panes and its corner grip resizes it,
the panes following.
A hamburger's content opens under its line inside the pane, not in a
popup: the hamburger shows it is open (aria-expanded, highlighted, the
panel attached to it); its button, the panel's X or Escape closes it.
A custom field's edit form and the + create form open inline the same
way and close only their panel when saved. The popup layer added in
9c9742e09d is removed, as nothing uses it now.
Docs: Card-Settings.md, Card-Fields.md and Board Settings / Card.md.
New tests cardSectionsOnCard, minicardSections, cardSettingsSplit,
cardSettingsInlinePanels and card-settings-sections-order.e2e.js; the
guards of the old layouts and orders say why they changed.
Thanks to xet7 !
A hamburger of Card Settings opened its menu in Card Settings' place:
the popup slid to the menu and Card Settings disappeared until its back
arrow. Its menus, and the forms it opens (a custom field's edit form,
the + under Custom Fields, Subtask settings), now open as a popup
LAYER: on top of Card Settings, next to the button that opened them,
with Card Settings visible under it and scrolled where it was.
Popup.open(name, { layer: true }), from inside a popup, pushes the
layer on the stack as before, so Popup.back() - the layer's X, Escape,
a form's Save - closes the layer alone. The popup template draws the
popup under it with the layer on top, outside the scrolling content;
depth counts the sliding popups only, so a confirmation opened from a
layer still slides. One layer at a time: another replaces it, its own
button closes it, a press elsewhere in the popup closes it.
A layer opens toward the middle of the popup: a hamburger at the end of
a line gets its right edge, the + on the left its left edge, so the
create form no longer ran out of the popup's left side where its Save
could not be clicked; below the button, or above when there is more
room, its height and width capped to the room
(models/lib/popupLayerPosition.js). A menu's checkboxes start at its
edge rather than .check-div's form indent.
tests/popupLayer.test.cjs runs the placement and pins the Popup wiring
and that every Card Settings menu opens as a layer;
card-add-fields.e2e.js checks Card Settings stays visible under the
menu, the menu sits by its button, toggles, closes on a press elsewhere
and by its X alone, and that the + form opens inside Card Settings.
Thanks to xet7 !
Card Settings is now only Board Settings / Card. The opened card has no
Card Settings button any more: it simply shows every field its board
shows on cards, empty or not.
- On this card, Show empty and Show empty fields are removed, with the
hamburgers' Select all / Select none rows, their handlers, helpers,
board methods (setCardFieldAlwaysShown, setShowEmptyCardFields), CSS
and the three translation keys. The card no longer reads its own
shownFields / hiddenFields, nor the board's cardFieldsAlwaysShown /
showEmptyCardFields; the schema keeps them so old documents validate.
- Every section has a checkbox before its title: whether the whole
section is shown on the chosen side, whatever its rows are ticked.
Stored per side in cardSettingsHiddenOnCard / cardSettingsHiddenOnMinicard
(setCardSettingsSectionShown, board admins). The opened card's view
of the board and a new minicard view read false for every row of a
hidden section, Scrum fields included; Card Settings itself reads the
raw board, so the rows keep their ticks.
- A section with one row on the chosen side is one line: one checkbox,
the name, one hamburger.
- A hamburger is drawn only when it holds something.
- Custom Fields: its items are the board's custom fields, in their sort
order, reordered by drag and drop within the section, with a + under
them that creates one. Show on Minicard sets a field's showOnCard,
Show on Card its alwaysOnCard; its hamburger edits the field. The
section's checkbox is the board's Custom Fields flag of that side.
- Card actions (Filter: Subtasks, Edit voting, Card kind, ...) live in
the card's own section hamburgers; Card Settings keeps only board
actions.
Docs: docs/Features/Cards/Card-Settings.md replaces
Card-Settings-On-Card.md; Card-Fields.md and Board Settings / Card.md
rewritten. New tests cardSettingsSectionShown and
cardSettingsCustomFields; the guards of the removed behaviour say why
they changed; card-add-fields.e2e.js covers section hiding per side, an
empty card showing every field, empty hamburgers, the one-row line, the
Custom Fields items and +, and that the opened card has no Card
Settings.
Thanks to xet7 !
On the rows of Card Settings - on the opened card and in Board Settings /
Card - and in their hamburgers' popups, a checkbox touched its label:
the rows' grid had lost its column gap with the old pane's CSS, and a
hamburger's option put the box and the text side by side with none.
Both now have a space's width (0.3em) between them.
A ticked box still touched the label, because a tick is drawn rotated
and reaches about 3px past its box. Left to right it is drawn 3px
further left, so a ticked box keeps the same space and the label does
not move when it is ticked; right to left the tick leans away from the
label and stays as it was.
tests/cardSettingsOnCard.test.cjs pins the gaps and the tick rule, and
card-add-fields.e2e.js measures at least 3px for ticked and unticked
boxes on the rows and in Card's hamburger, with every label in the same
place.
Thanks to xet7 !
Board Settings / Card had its own layout: an Admin Panel left menu of
sections and a pane with two lists, column headings and per-column Select
all. It now renders the same template as the opened card's Card Settings
(cardSettingsInline), with no card: Show on Minicard / Show on Card, the
sections as checklists, their rows as the items, and the same section and
row hamburgers. The popup's header is the title and the close button, so
the inline's own title and X are not drawn there, and a row's hamburger
has no On this card. The popup is 600px wide, about an opened card,
instead of 1200px.
The popup no longer registers the Card Settings handlers itself: the
template inside it does, and registering them on both would handle every
click twice. The left menu, the pane, the column Select all, the
in-place action forms and the personal-only mode are removed with their
helpers and CSS. A non-admin sees what a card shows them: the board's
rows disabled, their own rows (Labels text, List title) usable.
Dragging, on the card and in Board Settings / Card:
- With drag handles on, a row or a section is dragged only by its
handle; the rest of the line scrolls the page. With them off, by its
icon and name.
- A row dropped on another section goes back where it was and nothing
is saved: the sections are unrelated kinds of fields. jQuery UI fires
a delayed "out" just before "stop", so where it was dropped is read
in beforeStop.
Found while testing both open at once:
- Each Card Settings has its own Show radio group name; one name made
the card's and the popup's radios one group.
- A section hamburger's Select all acts on the Card Settings it was
opened from.
- Rows are keyed, so switching Show moves each row with its side instead
of relabelling rows in place a flush before they reorder, when a
click could land on the neighbouring field.
Docs: Card-Settings-On-Card.md, Card-Fields.md and Board Settings /
Card.md. The node guards that pinned the old layout now pin the shared
template, with negatives that the old layout and the double handler
registration are gone; card-add-fields.e2e.js compares the two views
section by section and row by row on both sides, and tests the drag
rules, and the specs that drove the old pane drive the new one.
Thanks to xet7 !
Three Southern Sotho strings called a data field "masimo", which means
farm fields: Show empty fields in Card Settings, the five fields of a
cron schedule, and the webhook's optional fields. They now use
"dikarolo", the word the file already uses for card and custom fields.
sesotho-popup-titles.e2e.js, which checks that Card Settings names data
fields as such, now reads Card's "Show empty fields" in Board Settings /
Card and caught the first. The cron and webhook wording is low
confidence; a Southern Sotho speaker's correction on Transifex replaces
it.
Thanks to xet7 !
meteor update --release 3.6-rc.1 failed with "No version of
shell-server satisfies all constraints" (and the same for
dynamic-import, allow-deny, ddp-server, facebook-oauth and
google-oauth): those six are WeKan's forks in packages/, made because
Meteor 3.6-rc.0 lacked the 3.5.3 security fixes, and pinned to rc.0's
versions.
Meteor 3.6-rc.1 carries the security fixes of 3.5.3 and 3.5.4, but not
what is WeKan's own in the forks: the Admin Panel / Problems reports of
refused attempts, and the crypto-RNG keys of dynamic-import and meteor
shell (rc.1 still makes them with Math.random()). So the forks are kept
and rebased: each is rc.1's package with WeKan's changes over rc.0's
package merged three-way onto it. The 3.5.3 hunks merged away, as rc.1
has them; what remains over rc.1 is only the hooks and the keys. The
versions are rc.1's with a _1 wrap number.
@meteorjs/rspack moves to 3.0.0-rc.2 with the release. The Dockerfile's
METEOR_RELEASE and the snap description follow. The experimental Bun
build's Meteor patch applies unchanged to 3.6-rc.1; prepare-meteor.sh
now checks for rc.1's tag commit and applies meteor-3.6-rc.1.patch, and
the rc.0 copy is removed.
tests/meteor353SecurityFixes.test.cjs and meteorForkRandomKeys.test.cjs
are moved to rc.1 with their assertions kept;
meteor-353-security.e2e.js passes on rc.1 in Chromium and Firefox.
Thanks to xet7 !
tests/cardShownFields.test.cjs still pinned the Scrum filter of the
opened card's sections as it was before #6830, which now requires the
board's Scrum visibility before asking the card.
Thanks to jullbo and xet7 !
A rebase rewrote the commits these entries link to, so the links named commits
that no longer exist and would have 404ed on GitHub. Repointed by
releases/fix-changelog-hashes.sh, which only touches a link no ref in this clone
can reach.
Thanks to xet7 !
With Board Settings / Card's "Show empty fields" on, every opened card
showed the "Scrum settings" heading and its sections although Board
Settings / Scrum had none of its fields turned on. The Scrum fields are
the card's only sections with no board flag in the template, and
orderedSectionsOf asked only whether the card shows the field, which
Show empty fields answers yes for every field. A Scrum section now
needs the board's Scrum visibility (boardAllows) first, as Members,
Vote and Poker already do.
Thanks to jullbo and xet7 !
Fixes#6830.
The opened card's "+ Add" button is now "Card Settings". Clicking it no
longer opens the Board Settings / Card popup: the button and everything
below it on the card are replaced by Card Settings itself, until the X at
the end of its title puts the card back. Top to bottom:
- Card Settings, with its X (close) at the right of the title.
- "Show" and two radio buttons, Show on Minicard and Show on Card, which
choose what the checkboxes below are.
- The sections (Card, Date, Time, ... Comments) drawn like checklists: the
section is the checklist's title, with a count of the ticked rows and a
hamburger; its rows are the checklist items. An item's checkbox is the
board-wide Show on Card or Show on Minicard of that field.
- A row's hamburger holds On this card and Show empty; a section's holds
Select all / Select none of each column, the section's card actions and,
for Card, Show empty fields, Ask for a reason and the Draggable table.
Sections and rows reorder by drag and drop like checklists and their items,
and by the Up and Down keys. A section's order is the board's new
cardSettingsSectionOrder (normalised by sectionOrder() in
models/lib/cardSettingsSections.js); a row's is the card's or minicard's
field order, as in the popup. Sections collapse like checklists, kept per
user and board. The rows, checkboxes and handlers are the popup's, shared
rather than copied, and Board Settings / Card still opens the popup.
Design: docs/Features/Cards/Card-Settings-On-Card.md.
New strings: "Show" (card-settings-show) and the two hamburgers' titles,
"Actions". Every language takes its existing translation of the same
words; "Show" was written directly, with low confidence, for an, ary, bi,
csb, cy, cy-GB, fur, hsb, hy, jv, ka, kk, km, km-KH, km_KH, kok, lb, lld,
mai, mn, oc, or_IN, pa, sc, sn, so, szl, te-IN, tg, tk_TM, tl, tpi and tt.
Tests: tests/cardSettingsOnCard.test.cjs (the order rules, the layout, the
shared handlers, the translations) and
tests/playwright/specs/card-add-fields.e2e.js rewritten for the card's Card
Settings, the popup's own tests now opening it from Board Settings. The
guards that pinned the card template's indentation and the old opener say
why they changed.
Thanks to xet7 !
customfield-dead-board counted every custom field that names a deleted
board, but its Repair only removes dead boards from a field that is also on
a live board; a field whose boards were ALL deleted was skipped on purpose,
since removing them would leave it on no board. So those fields stayed in
the count with a Repair button that could never change it: one instance
showed 510 and "Repaired: 0" after every click.
- New check customfield-boards-deleted: fields whose boards no longer exist
(through boardIds or the legacy boardId). No existing board shows them.
Repair deletes the definitions after the confirming second click, like
the other repairs that delete data. A value a card on a live board still
holds is left alone and stays listed under card-dead-field-values.
- customfield-dead-board counts only fields that still have a live board,
so its count goes to zero when its Repair has run.
docs/Features/Admin-Panel/Problems/Data-Health.md lists the new check.
Test: tests/dataHealth6818.test.cjs.
Thanks to Raymond-Voogt and xet7 !
Fixes#6829.
The page listed Trello's Email Inbox, Complete Card Checkbox, Workspaces,
Butler scheduled and repeating tasks and Card Repeater as not yet in WeKan,
and Sync Lists and Map Card as WeKan ideas not yet implemented. Each now has
the WeKan release that added it: Workspaces v8.17 (2025-11-06), the complete
checkbox and card location v9.42 (2026-06-13), Scheduled Rules and recurring
cards v9.46 (2026-06-15), list sync v11.69 (2026-09-11), the Map view v12.10
(2026-09-29) and Email to card v12.40 (2026-10-14). The links to the 2018 and
2021 releases point to old-CHANGELOG/, where those sections are now.
Thanks to xet7 !
A list's Add card form stayed open after a card was added, for the next
card, which the reporter found unexpected. It now closes once the card - or
every card of a multi-line paste - is added. Member Settings has a new
switch, "Keep the Add card form open after adding a card", off by default,
for who types several cards in a row. The close happens after the last
title, not at submit, because addCard still reads the form between the
titles of a paste; a failed add keeps the form and its text.
The two new strings are translated into every locale. Low confidence: ace,
ak, am, an, ary, as, ast-ES, ay, ba, bho, bi, bm, bo, br, bua, chr, ckb, co,
csb, cv, dz, ee, ff, fj, fo, fur, gn, gv, ha, haw, hsb, ig, iu, kl, kok, ks,
kw, lb, lg, lld, mai, mg, nah, nap, nd, nso, ny, om, or_IN, pap, ps, qu, rn,
rup, rw, sah, sc, scn, sd, se, sm, sn, so, ss, st, szl, ti, tig, tlh, tn,
to, tpi, ts, uz-AR, ve, ve-CC, ve-PP, vo, wa, wa-RR, wal, wo, wuu-Hans, zgh.
tests/keepAddCardFormOpen6827.test.cjs pins the switch, its default and
where the form is closed, with negative cases for a paste and a failure.
tests/playwright/specs/keep-add-card-form-open-6827.e2e.js drives it: closed
after one card and after a paste, kept open with an empty title once the
switch is on. multiline-titles opens the form again for its second card, and
assist-due-from-title keeps it open to check the next card's form has no
leftover hint.
Thanks to brlin-tw and xet7 !
Fixes#6827,
tests/bunSmokeMongo.test.cjs required the workflow's docker run to say
mongo:8, the floating tag that refused to start on the runner's 6.19+ kernel
in v12.66. It now requires mongo:9.0.4, and fails if mongo:8 comes back.
Thanks to xet7 !
linux-x64: mongo:8 refused to start on the runner's kernel ("Linux kernel
versions 6.19 and newer has a known incompatibility"). The smoke test pins
mongo:9.0.4, which recognises Ubuntu kernels that have the fix
(docs/Databases/Supported.md).
linux-arm64: Chromium was installed into ~/.cache/ms-playwright, while
build-target.sh loads tools-env.sh, which points Playwright at
.tools/ms-playwright, so the browser smoke test found no browser. The install
step uses the same path.
Thanks to xet7 !
tools-env.sh made .tools/tmp only in a git checkout or where .tools already
existed, so that scripts mounted alone into a container (/releases) do not
make a /.tools there. The wekan-ondra and wekan-gantt-gpl snaps build from a
copy of the tree without .git (/root/project), so tools-env.sh found no
TMPDIR, and every fetch.sh in snapcraft.yaml stopped at "TMPDIR: unbound
variable" - reported as the Caddy download failing (v12.66). A tree with
package.json beside releases/tools-env.sh is a source tree too.
tests/toolsDirOnly.test.cjs sources it under set -euo pipefail with no
TMPDIR in such a copy, and, as the negative case, in releases/ alone, where
no .tools/ is made and no TMPDIR is invented.
Thanks to xet7 !
sandstorm-src/build-deps.sh still built meteor-spk.deps in
~/projects/meteor-spk, and the workflows put that on PATH, while
sandstorm-slim-spk.sh had moved to .tools/projects/meteor-spk with
aeff4900df: "no meteor-spk.deps at .tools/projects/..." and the v12.66 .spk
did not pack. build-deps.sh sources tools-env.sh and defaults to
WEKAN_METEOR_SPK_DIR, and the Sandstorm, Sandstorm store and meteor-spk
workflows use $GITHUB_WORKSPACE/.tools/projects/meteor-spk.
tests/toolsDirOnly.test.cjs scans sandstorm-src/ too, and fails on a workflow
that names ~/projects.
Thanks to xet7 !
The memory budget read _memory_mb=$(awk ... /proc/meminfo) with no fallback
since 1f9ada5599. awk fails where there is no /proc, and under set -eu an
assignment from a failed substitution ends the script without a word, so the
FreeBSD and OpenBSD bundles and the Mac app never started WeKan (v12.66).
It is `|| true` again, and sysctl then gives the memory.
tests/macosShellPortability.test.cjs runs the memory lines under set -eu
without /proc and requires a number, with the unguarded line as the negative
case, and fails on any set -e script with a /proc read in $(...) that has no
fallback.
Thanks to xet7 !
install-node-for-arch.sh refuses to run without TMPDIR, because the
container's own /tmp is a small part of its writable layer.
repack-bundle-for-arch.sh mounts this checkout's .tools/tmp as /tmp and sets
TMPDIR; the workflow's own docker run did not, so armhf, armv6, armv7, i386,
ppc64le, riscv64 and s390x all stopped at "TMPDIR must be the mounted
.tools/tmp" in v12.66, and win32, which builds from the i386 bundle's
Node.js, had nothing to build with.
Thanks to xet7 !
- assist-search-phrases: the feature is turned off in the database, and a
search sent before the browser had the published setting still ran the
phrase. The search is repeated until the browser has it; a phrase that is
never turned off still fails.
- board-settings-columns: a non-admin member's count of 0 passed in Chromium
before the pane had rendered, and in Firefox counted Show on Card's
Select all / Select none, which share the class and are a member's like the
checkboxes under them. It waits for the pane and counts only the board-admin
column actions.
- card-drawing-canvas: a maximized drawing fills the window, so its corner
handle is at x 1236 of 1280 and a drag 120px further was not delivered by
Firefox. The drawing is zoomed out to 80% first, which leaves room.
Thanks to xet7 !
The report lists at most 500 findings and counts the rest. They were listed
first come, first served, so on the test server 14358 baseline-move-candidate
rows of the integrity baseline took all 500, and the extension-mismatch and
mime-mismatch a type check is run to find were only numbers in the totals -
the Firefox stage's file-status-audit.e2e.js failed on it, and an
administrator would not have seen which file it was.
When the list is full, a kind with fewer rows listed now takes the place of
the last row of the kind with the most, so every kind found is listed and the
totals still count all of them. fileStatusAudit.test.cjs: 8 rows are 8
kinds with 40 orphan files crowding them, every finding is listed or counted
as omitted, and with room for all nothing is dropped.
Thanks to xet7 !
The previous fix assumed the Drawing popup grew to its drawing's width and
ran off the window. Measured in the rebuilt app, it did not grow at all:
every popup is min(380px, 55vw) wide, the drawing's min(820px, 90vw) overflowed
inside it and the popup clipped it - the larger pen sizes and Save were cut
off by the popup, not by the window. Its box never changed, so the
ResizeObserver that keeps a movable popup inside had nothing to react to.
popup.css gives the Drawing popup min(860px, calc(100vw - 20px)), not
!important so that maximized `width: auto !important` still wins. Opened from
the card's menu at the right of a 1280px window it is now 860px wide at
x 408, 12px from the edge.
popup-drawing-window.e2e.js waits for the drawing and a box that stays put
before measuring: the Drawing popup is the card menu's popup element renamed,
which for a moment still had the 960px menu's place, and a drag pressed at
that place selected the page's text and closed the popup. The first drag
goes toward the side that has room.
Thanks to xet7 !
Two faults the Chromium stage found, measured in the browser:
- A maximized new drawing was sized to its stage (1212 x 563 in a
1280 x 720 window) but drawn 784px wide. popup.css still had the rule of
the older design that kept the drawing at 800 x 500's shape -
width min(100%, (100vh - 230px) * 1.6), aspect-ratio 8 / 5 - which
overrode the surface's own size. d5d027a6f2 replaced that design; the
rule is removed. (2929b61c85's refit was right, and is kept.)
- Opened from the card's menu at the right of the window, the Drawing
popup grows to min(820px, 90vw) in the same popup the narrow menu was
placed for, and its right part - the larger pen sizes and Save - was
cut off by the window until it was dragged. A movable popup that grows
is now kept inside the window by the rule a drag keeps it there, 12px
from each edge (client/lib/popupBounds.js); not while maximized.
tests/popupBounds.test.cjs drives the bounds and fails on the 8 x 5 rule
coming back; popupMoveMaximize and popup-drawing-window.e2e.js pin the
drawing taking the window when maximized instead of its old shape.
Thanks to xet7 !
c8a15b514f moved the headings' smaller font from the heading line to its
text (.card-settings-pane-head > span, sidebar.css): on the line, the
em-sized columns shrank and each heading drifted off its cells. The
clean-task-boards spec still measured the line, which inherits the
body's 11.2px, and failed in Chromium; it measures the text, 9.6px. All
four clean-board specs pass.
Thanks to xet7 !
client/lib/autofocus.js focused an [autofocus] element whenever a re-render
added one: the LAST [autofocus] anywhere in the document, selecting its
text, 50 ms later - and decided before those 50 ms whether it was already
focused. On the import page, typing the attachment token after pasting a
Jira export moved the focus to the pasted JSON, all of it selected, and the
token replaced it: the import then said "Your text is not valid JSON"
(import-attachment-auth.e2e.js in Chromium). A focus() then select() on the
textarea, recorded in the browser and found at autofocus.js in the running
bundle, showed it.
Now only an [autofocus] element that was itself just added is a candidate,
and whether to focus it is decided when the focus happens: never while
the user is typing in another field (an input, textarea, select or
contenteditable). Opening a popup or a form still focuses its field -
focus is then on the button that opened it, or on a composer field the
re-render removed. client/lib/autofocusDecision.js is the decision;
tests/autofocusDecision.test.cjs drives it with stand-in elements.
Thanks to xet7 !
CardPage._selectBoard asked whether the popup had a boards select with
count(), which answers at once. Asked while the popup was still rendering,
it said 0 - which the helper reads as "a worker, who has no boards
select" - so the move went to the named list of the SAME board, and
#6793's Move Card spec waited in vain for the card's new boardId. A trace
showed it: one select, and that for the list. The helper now waits for
the lists select, which every picker draws, before asking about the
boards one. The #6793 specs and 03-cards-operations (29 tests) pass in
Chromium.
Thanks to xet7 !
Each storage setting (S3, Azure, Google Cloud, WebDAV ...) shows its
description and menu path in English and in the user's language, so the
product's own terms stay recognizable. The English line was typed into
the template a second time above the {{_ key}} line: in English, and in
every language without that translation yet, the same sentence stood
twice in a row - which is also why Playwright's strict getByText found
two and storage-settings-translations.e2e.js failed in Chromium.
The English now comes from en.i18n.json (englishText; all 26 typed copies
were identical to it, so no text changes), and the translated line is
shown only when it differs (translatedDiffers), reactively with the
language. tests/attachmentSettingsBilingual.test.cjs fails on a typed
English twin coming back and on a translation shown unconditionally.
Thanks to xet7 !
Both failed in Chromium on their negative case: an upload onto one's own
card answered 403 "Please sign in to upload.", and a checklist without a
foreign stamp "Access denied". The guards were right. The attempt came
first, and a signed-in account that attempts a vulnerability is blocked
(server/lib/blockOnSecurityEvent.js): loginDisabled, and the page's
session ended - so the legitimate call that followed ran signed out.
Printing Meteor.userId() around the first call showed it.
Each spec now does the legitimate case first and the attempt last, and
checks that the attempt blocked the account (services.securityBlock names
UploadBleed or StampBleed). The stamped checklist comes before the
stamped upload: its refusal names no account, so the user is still
signed in for the upload. Both pass in Chromium.
Thanks to xet7 !
The chart's commented reference of every WeKan setting was copied from
docker-compose.yml by hand once and had fallen 74 settings behind, which
tests/chartsFerretdb.test.cjs reported as soon as .tools/charts was there.
releases/charts-values-reference.mjs regenerates the block from the wekan
service's environment, and release-charts.sh runs it on every chart
release. A comment after a value goes on the line above, not into the
value.
The chart change itself, and the 512 MiB request / 2 GiB limit #6606 had
asked of it, are committed in wekan/charts. All 19 chart tests pass.
tests/chartsValuesReference.test.cjs runs the generator on a temporary
values.yaml: every compose setting written, nothing outside the block
touched, no comment inside a value, and a second run changes nothing.
Thanks to xet7 !
The Windows counterpart of the Linux Flatpak sandbox and the macOS
sandbox-exec one. Windows Sandbox is a disposable Hyper-V virtual machine
that comes with Windows 10 and 11 Pro, Enterprise and Education; it sees
nothing of the host but the folders mapped into it.
- docs/Security/Sandboxes/Windows/sandbox.ps1 (on the host; sandbox.bat
starts it) offers to turn the Windows Sandbox feature on, copies
user.name and user.email into the checkout's .git\config, and writes
.tools\sandbox\wekan-vscodium.wsb: the checkout as the only mapped
folder, read-write at the same path under the sandbox's user; network
on; microphone, camera and printers off; ProtectedClient; half of the
host's memory, 4 to 16 GiB.
- sandbox-logon.ps1 runs inside at logon: TEMP, TMP and TMPDIR in
.tools\tmp, git's configuration in .tools\home, PortableGit and the
VSCodium zip downloaded once into .tools - each verified (VSCodium's
.sha256, the SHA-256 in Git for Windows' release notes) before it is
unpacked - and VSCodium opened on the checkout, its settings and
extensions in .tools\vscodium.
- README.md is the design: what is refused and allowed, how, and the
trade-offs.
Not run on Windows yet: written and reviewed on macOS, where PowerShell is
not available. tests/windowsSandbox.test.cjs reads the scripts and pins
the single mapping, the .tools locations, verification before unpacking,
and that native commands cannot stop Windows PowerShell 5.1.
Thanks to xet7 !
The macOS counterpart of the Linux Flatpak sandbox
(docs/Security/Sandboxes/vscodium). docs/Security/Sandboxes/macOS/sandbox.sh
installs VSCodium with Homebrew when it is missing, writes a Seatbelt
profile for this checkout's path to .tools/sandbox/vscodium-macos.sb, a
Finder launcher beside it, and starts VSCodium under sandbox-exec:
- every file under /Users and /Volumes is refused - the home directory,
~/.ssh, ~/.gitconfig, other users and /Users/Shared, other disks - except
the checkout, and the login session's ssh-agent socket is refused;
- HOME is .tools/home, TMPDIR .tools/tmp, VSCodium's settings and
extensions are in .tools/vscodium, and env -i drops SSH_AUTH_SOCK and an
editor's ELECTRON_RUN_AS_NODE;
- --no-sandbox, because Chromium's own helper sandbox cannot start inside
sandbox-exec ("Operation not permitted"); every helper is inside this
one instead;
- user.name and user.email are copied into the checkout's .git/config,
since ~/.gitconfig cannot be read inside.
Verified on macOS 27 with --check (home, ~/.ssh, ~/.gitconfig and
/Users/Shared refused; the checkout, git and the network allowed) and by
starting VSCodium in it: the window, its helpers and the extension host
ran. README.md is the design: what is protected, how, and the trade-offs.
tests/macosSandbox.test.cjs pins the rules and, on macOS, runs a profile
against a shell.
Thanks to xet7 !
Syncing wekan/wekan cloned the whole GitHub repository - 365,000 objects,
500 MB - into .tools/wekan-github, although it is this checkout
(~/repos/wekan on Linux, ~/Documents/repos/wekan on macOS,
Downloads\repos\wekan on Windows). Only the destination mirrors
(wekan-gitlab, wekan-codeberg ...) need checkouts of their own.
The source's branches and tags are fetched into this checkout instead,
under the private namespace refs/mirror-source/<source>/, which already
holds nearly every object: 1.7 seconds for 1,278 refs, measured. That
namespace is not a branch; the working tree and local main are untouched,
so unpushed local commits never reach a mirror. The fetched refs are
deleted again in a finally, after the push and also when the fetch, the
push or the merge-preserving retry fails.
tests/activeForgeMirror.test.cjs and tests/mirrorGitDirectories.test.cjs
run syncGit against a stand-in checkout (WEKAN_MIRROR_SOURCE_CHECKOUT for
the CLI), assert that GitHub's wekan is not cloned, that only the
destination is, and that the source refs are cleared - also after a failed
fetch.
Thanks to xet7 !
releases\tools-env.bat is the counterpart of releases/tools-env.sh: TEMP,
TMP and TMPDIR in .tools\tmp, npm's cache and global installs, Go's caches
and the Playwright browsers in .tools\, METEOR_WAREHOUSE_DIR at
.tools\.meteor once Meteor is there, and those tools first on PATH, so no
user or system PATH setting has to change. build.bat and every
releases\ batch file call it; the bundle launchers (start-wekan.bat) do
not, as they run a finished bundle where there is no .tools\.
build.bat's install option no longer installs a system-wide Node.js with
Chocolatey: it unpacks the Dockerfile's version from nodejs.org's zip
into .tools\node-v<version>-win-<arch>, verified against SHASUMS256.txt
first, and Meteor goes into .tools\.meteor (LOCALAPPDATA is .tools for the
installer only). build-bundle-win64.bat works in .tools\tmp\win64-bundle-
<version> and says where the zip is. tools-env.sh also keeps Go's caches
and the Playwright browsers in .tools/, as tools-env.bat does.
tests/toolsDirOnly.test.cjs pins tools-env.bat, its callers and the .tools
install; tests/buildScriptParity.test.cjs reads the Playwright directory
and Node.js version from their new places.
Thanks to xet7 !
build.sh and the releases/ scripts wrote all over the machine: temporary
files to /tmp and macOS's /var/folders, downloads and release zips to
~/Downloads or ~/Lataukset, Meteor to ~/.meteor, Node.js to /usr/local
(sudo n) or ~/.nvm, the npm cache to ~/.npm, Volta to ~/.volta, the
Sandstorm toolchain to ~/projects, and PATH lines into ~/.zshrc and
~/.bashrc. A sandboxed editor (Flatpak on Linux, sandbox-exec on macOS)
can reach only the checkout, and a container's own /tmp is a few hundred
megabytes. Everything is in .tools/ now, which git and Meteor ignore:
- releases/tools-env.sh, sourced by build.sh and every script that writes
files, sets TMPDIR/TMP/TEMP to .tools/tmp (also downloads and release
zips), NVM_DIR, npm_config_cache, VOLTA_HOME and the meteor-spk
directory to .tools/, METEOR_WAREHOUSE_DIR to .tools/.meteor once Meteor
is installed there, and puts those tools first on PATH, so no shell rc
file is edited. HOME is not moved: git needs ~/.gitconfig and ~/.ssh.
A TMPDIR already inside a .tools/ is kept, so a test's own directory
there stays its own; without a writable one it stops rather than fall
back to /tmp. It never fails a caller running set -euo pipefail.
- macOS's mktemp ignores TMPDIR when given no template, so every mktemp
names one: mktemp "$TMPDIR/name.XXXXXX". Fixed /tmp files are in
$TMPDIR.
- build.sh installs Node.js on Linux into .tools/n (N_PREFIX, no sudo),
nvm on macOS into .tools/nvm (PROFILE=/dev/null: no rc file edited), and
Meteor with HOME=.tools for the installer only, into .tools/.meteor.
build.sh is replaced, not edited in place, while it may be running.
- Containers get this checkout's .tools/tmp as their /tmp: every
db-conformance database (MariaDB's temporary tables were verified to land
there, and MongoDB's socket) and the repack container, which empty it
themselves before they are removed. install-node-for-arch.sh requires
the mounted TMPDIR.
- tests/run-node-suites.cjs gives every suite TMPDIR in .tools/tmp, and the
tests that copied a script into a fixture copy tools-env.sh too; tests
that pinned ~/.meteor, the rc file edits, sudo n or /tmp/manifest.json
pin the .tools/ places now and say why.
tests/toolsDirOnly.test.cjs runs tools-env.sh against a TMPDIR already
set elsewhere and under set -euo pipefail, and fails on any script that
makes a temporary file without naming .tools/tmp, writes to the home
directory, or edits a shell rc file, or on a container whose /tmp is its
own.
Thanks to xet7 !
The checkout is at ~/repos/wekan on Linux, ~/Documents/repos/wekan on macOS
and Downloads\repos\wekan on Windows (CLAUDE.md). release.sh,
release-bundle.sh, release-snap.sh, release-sandstorm.sh, release-cleanup.sh,
install-sandstorm.sh, node-update.sh, docker-push-gantt.sh, up.sh and
rel.sh hard-coded Linux's ~/repos/wekan (rel.sh also /home/wekan/repos), so
on a Mac they cd'd into nothing - or into a stale copy - and edited or
packed the wrong tree. Each finds the checkout from its own location now
(REPO_DIR, as mirror.sh already did), which is right on every system; the
parent directory is $(dirname "$REPO_DIR").
DOWNLOAD_DIR defaulted to ~/Lataukset, the Finnish Linux name of Downloads.
macOS keeps ~/Downloads in every language, so it is Lataukset where that
exists and Downloads otherwise.
The VirtualBox appliance scripts keep /home/wekan: that image is a Linux VM
with its own fixed layout. tests/repoLocationPerOs.test.cjs pins the three
locations in CLAUDE.md and AGENTS.md and fails on any script that
hard-codes one system's checkout path or assumes Lataukset.
Thanks to xet7 !
wekan.fi, charts and docker-cleanup-volumes used to be checked out in ../w/
beside the repository, and several places kept that as a fallback after
they moved to .tools/ (CLAUDE.md). With no .tools/wekan.fi, the Hall of Fame
tests read a stale ../w/wekan.fi and failed on markup the site no longer
has.
- tests/hallOfFameProblemsCoverage.test.cjs and
tests/fixedVulnerabilityClasses.test.cjs read only .tools/wekan.fi (and
skip, saying so, when it is not there).
- release-charts.sh, backfill-charts.sh, reindex-charts.py and version.sh
look for the charts checkout only in .tools/charts.
- docker-build.sh clones and runs docker-cleanup-volumes in .tools/.
tests/noLegacySiblingRepos.test.cjs fails when a script, test or document
under releases/, tests/, docs/ or tools/, or at the root, looks in ../w/
again; tests/reindexCharts.test.cjs now requires ../w/charts to be gone.
Thanks to xet7 !
A Node.js can be compiled without single executable applications - the
Homebrew one on macOS is - and `node --build-sea` then only says "Single
executable application is disabled" and exits 9, which the build
reported as a bare failed command. build.mjs asks first (seaSupported) and
stops with what to do: build with a Node.js from nodejs.org, given with
--host-node.
tests/singleFile.test.cjs skips the end-to-end build on such a Node.js
and says why, tests seaSupported against stand-ins of both kinds, and
expects the 32-bit refusal to name the host's own arch - it expected
"x64", so it failed on an arm64 Mac.
Thanks to xet7 !
macOS and the BSDs have no /proc.
- releases/db-conformance.sh decided whether a container's run was still
going from /proc/<pid>/cmdline. On a Mac every live run looked dead, so
a second conformance run removed the first one's containers and
networks. The command line comes from ps -o command= where there is no
/proc (tests/dbConformanceStaleContainers.test.cjs passes on macOS now).
- start-wekan.sh and releases/ferretdb/start-wekan.sh sized the Node.js
and FerretDB heaps from /proc/meminfo, so on a Mac they assumed 2 GiB
whatever the machine had. They ask sysctl hw.memsize (macOS) or
hw.physmem (BSD) there, as build.sh already did.
- releases/debug-speed-watch.sh shows the load average from sysctl
vm.loadavg where there is no /proc/loadavg.
tests/macosShellPortability.test.cjs fails on a /proc memory, load or
command-line read without such a fallback (the Docker entrypoint, which
runs only on Linux, excepted). releases/nextcloud-pack.sh is listed in
tests/buildScriptParity.test.cjs as run by nextcloud.yml.
Thanks to xet7 !
tests/stripNode.test.cjs built its ELF fixture with `gcc`, which on macOS
is clang and writes a Mach-O, so the section reader found nothing and the
test failed with "the fixture has symbols and debug data". The ELF cases
now skip unless the compiler really wrote an ELF, and a new case runs
releases/strip-node.sh on a real Mach-O where there is a Mac: the local
symbol is gone, the exported one stays, the binary is re-signed ad hoc,
passes codesign --verify --strict and still runs.
Thanks to xet7 !
Started from zsh on macOS, a script gets bash 3.2 as /bin/bash and BSD
tools. Every .sh at the repository root and under releases/ was checked
with bash 3.2 and for GNU-only tool options; what failed:
- test-menu.sh: declare -A (bash 4) is a case function, runner_for, and
an empty match list no longer trips set -u in bash 3.2.
- releases/ferretdb-latest-tag.sh: with no GitHub token, "${auth[@]}"
was an unbound variable under set -u in bash 3.2; it uses
${auth[@]+...} as ferretdb-asset.sh already did.
- find-replace.sh: never worked anywhere ('$1' in single quotes searched
for the text $1) and used GNU grep -Z and sed -i. It replaces literal
text with perl now, skipping the directories find.sh skips.
- sha256sum (missing on older macOS) falls back to shasum -a 256 in
release-x2.sh, repack-bundle-for-arch.sh, install-node-for-arch.sh and
sandstorm-capnp-schemas.sh; -c works with both.
- sandstorm-capnp-schemas.sh: nproc falls back to sysctl hw.ncpu, and
sed -i takes a .bak suffix (GNU and BSD alike) that is then removed.
- sandstorm-slim-spk.sh: ln -sfT is ln -sfn, stat -c%s is wc -c.
- virtualbox/ipaddress.sh: ifconfig where there is no ip.
tests/macosShellPortability.test.cjs parses every bash script with
bash 3.2 when /bin/bash is 3.x, and fails on any of these shapes coming
back: associative arrays or mapfile, flock without a fallback, tar
--transform, a GNU-only sed -i, sha256sum / nproc / ip without a
fallback, ln -T or stat -c.
Thanks to xet7 !
The UCS App Center preinst hook locked with flock(1), which is util-linux
and missing on macOS and the BSDs, so tests/ucsMigration.test.cjs could not
run the hook there ("flock: command not found"). Without flock it now takes
the same lock with perl's flock() on the inherited fd 9: a flock(2) lock
belongs to the open file, so it is held until the hook closes fd 9, exactly
as flock -n 9 holds it. Verified: a second locker is refused and the lock
frees when the first one closes. perl is on UCS (Debian) and macOS.
The Nextcloud ExApp archive was packed with GNU tar's --transform, which
bsdtar (macOS, the BSDs) does not have. releases/nextcloud-pack.sh copies
the source under a staging <app-id>/ and packs that directory, with the
same exclusions, using only options both tars share; COPYFILE_DISABLE=1
keeps macOS tar from adding ._* AppleDouble files. The workflow and
tests/nextcloudRelease.test.py both use it, and
tests/ucsNextcloudWorkflows.test.cjs fails if --transform comes back.
Thanks to xet7 !
card-settings-both-sides read row[side].needsCard for every row, but six
rows have no minicard side by design (suggested members, checklist from
description, attachment previews and details, Drawing, action items), so
it threw "Cannot read properties of undefined". It expects what
rowsForSide lists now: rows that have the side, less the card-only List
title.
The Scrum settings title spec read the pane title right after clicking
Scrum. Card Settings opens on Card now, so it read "Card" and failed on
the "Card button" label. It waits for the pane title to be Scrum's menu
name first.
Both failed in the Chromium stage of the 2026-10-27 EVERYTHING run.
Thanks to xet7 !
API JSON uploads and the site logo stored again, the validate-and-move
method moves again, REST card placement checked, WIP limits admin-only,
AuthBleed attempts recorded and Data health's "Never" with a key of its
own - each removed from TODO Later now that its fix is in Upcoming. The
browser runs and WebKit stay there.
Thanks to xet7 !
The applyWipLimit, enableWipLimit and enableSoftLimit methods have always
required a board admin ("You must be a board admin to modify WIP
limits."), but a member with write access could still set wipLimit with a
plain list update - the Lists allow rule grants write members every field
- or with REST PUT /api/boards/:boardId/lists/:listId. A WIP limit is a
board-wide workflow rule, so the methods' rule now holds everywhere:
- a Lists deny rule refuses an update touching wipLimit unless the caller
is admin of the list's board. Every WeKan client path goes through the
methods, so it refuses nothing in ordinary use;
- the REST PUT asks for a board admin (checkBoardAdmin) when the body
carries a wipLimit; write access still edits title, color and starred.
tests/wipLimitAdminOnly.test.cjs runs the real deny rule, pins the REST
check before the write, and fails on any list method that changes a WIP
limit without a board admin check first.
This was in CHANGELOG TODO Later as needing a maintainer decision;
decided: board admins only.
Thanks to xet7 !
linkedWritePolicy sliced POST cards' linked-card branch up to the getCard
that #6823 removed, so the slice ran to the end of the file and found
checkBoardAccess elsewhere; it ends at the branch's author-spoof note now,
and fails if that is missing. webhookBleed (GHSA-7w2h-g83c-jqrp) pinned
copyBoard's signed-out refusal as one line; it records an AuthBleed
attempt first now, so the guard accepts the block and still requires the
refusal before the board is read.
Thanks to xet7 !
The Data health status line ("Last Run: Never") reused List Sync's
list-sync-last-synced-never, so a translator who words that one as "never
synced" would make Data health read wrong. It is data-health-never-run
now, added after data-health-repair with add-pending-keys.mjs
--all-locales (the completeness suites require every locale to carry
English's keys in order) and listed in pending-transifex.json.
Every locale already translates the identical English "Never" under
list-sync-last-synced-never; fill-translations.mjs --apply filled the new
key from that in 234 locales, writing only the English placeholders. A
real Transifex translation still replaces it on the next pull.
This was in CHANGELOG TODO Later as needing a maintainer decision.
Thanks to xet7 !
copyBoard, copyCard, copySwimlane and createCardWithDueDate - four of the
AuthBleed methods - refused with plain not-authorized errors, so an
attempt left no trace in Admin Panel / Problems. AuthBleed was on the
PENDING list of tests/hallOfFameProblemsCoverage.test.cjs.
A new catalog key, authz.copy (AuthBleed, high, CWE-862), is recorded
where no legitimate client can be:
- a signed-out caller of any of the four - no WeKan client copies while
signed out, and with no userId nobody can be blocked;
- any refusal of createCardWithDueDate, which no WeKan client calls.
The membership refusals of copyBoard, copyCard and copySwimlane are
deliberately NOT recorded: a member who is not admin of a shared or
default board template, or a stale Copy Card / Copy Swimlane dialog,
reaches them in ordinary use, and a high-severity record naming the
account disables that login (server/lib/blockOnSecurityEvent.js). Each
record is wrapped so logging cannot break the guard.
tests/authBleedCopyMethods.test.cjs runs the real method bodies and pins
both: what is recorded, and that the ordinary-use refusals are not.
AuthBleed leaves the PENDING list.
This was in CHANGELOG TODO Later as needing a maintainer decision.
Thanks to xet7 !
The JSON branches of REST /api/attachment/upload, upload-background and
copy, their Meteor-method twins in server/attachmentApi.js, and Admin
Panel's site logo upload created the file with Attachments.insertAsync.
Meteor-Files (ostrio:files 3) has insertAsync only on the client; on the
server it is not a function, so every one of them failed - a REST 500
"Attachments.insertAsync is not a function". Only the multipart branches,
which use addAttachmentFromStream, worked.
All seven store with writeAsync(buffer, { fileName, type, userId, meta },
true) now, as email-to-card and the importers already do. Its
after-upload hook validates the file - type, size and the external
scanner, which these paths skipped - and moves it to the default storage,
or to meta.copyStorage for a copy, so the second deferred moveToStorage
each branch made is removed. The file records who stored it.
tests/attachmentApiServerWrite.test.cjs pins the seven calls and fails on
any server-side Attachments.insertAsync or Avatars.insertAsync.
This was in CHANGELOG TODO Later as needing a maintainer decision; the
site logo upload was found by the new test's tree-wide check.
Thanks to xet7 !
After validating, the method moved the file from Meteor.defer by calling
the moveAttachmentToStorage method. A deferred call has no method
invocation and so no userId, and that method's first check is "You must be
logged in.": every move was refused, the error went nowhere, and the file
stayed where it was.
Every check moveAttachmentToStorage makes - a site admin, the board
visible, the destination on the allowlist - is made by this method before
the defer, so the deferred step now calls moveToStorage itself, as the
upload path already does, and logs a failure instead of dropping it.
tests/moveStorageBleed.test.cjs pins it; its existing guard still requires
the admin check and the allowlist before every caller-named move, and a
new one fails on any Meteor.defer or Meteor.setTimeout in server/ or
models/ that calls a Meteor method.
This was in CHANGELOG TODO Later as needing a maintainer decision.
Thanks to xet7 !
POST /api/boards/:boardId/lists/:listId/cards and its bulk sibling wrote the
URL's listId and the body's swimlaneId onto the new card without checking
either against the board. A list or swimlane id of ANOTHER board gave a
card that pointed at a list and a swimlane its own board does not have -
the #6572 shape, a card that shows on neither board - and the list
permission check was evaluated against the wrong board's rules. The PUT
route has refused this since #6572; the create routes now do the same with
a 400 that names the field, and the bulk route reports it per entry. An id
that is not a string (such as { $ne: null }) is refused rather than looked
up. The linked-card branch was already checked by createLinkedCardFor.
tests/restCardPlacement.test.cjs pins both routes and fails on any REST
handler in server/ that inserts a card without the check.
This was in CHANGELOG TODO Later as needing a maintainer decision; the PUT
route's #6572 rule already decided it.
Thanks to xet7 !
The six fixes from Raymond-Voogt's reports #6821-#6826. CHANGELOG.md went
over its 450,000-byte budget with them, so v12.40 is moved to
old-CHANGELOG/2026/10.md by releases/changelog-archive.mjs.
Thanks to Raymond-Voogt and xet7 !
The database health probe (server/lib/databaseHealth.js) saw restarts, slow
storage and a full disk, but not what made one instance slow: a 10 GB
collection scanned three times per search, and an hourly job reading all
1.1 million cards (#6824, #6825). Both were visible only in MongoDB's own
log, as "Slow query" lines with COLLSCAN, and in serverStatus: 1.6 billion
documents read in two hours.
Two more checks, every 5 minutes with the others:
- db.collection-scan: the "Slow query" lines MongoDB keeps (getLog
'global'), newer than the last probe, where a query read a whole
collection of at least 10,000 documents. Grouped by collection and the
field names filtered on - never values - with count, documents read and
time. Small tables read whole by design (settings, roles) and internal
namespaces are left out. Needs the getLog privilege; without it, and on
FerretDB, the check finds nothing and is skipped.
- db.scan-rate: serverStatus.metrics.queryExecutor.scannedObjects between
two probes above 50,000 documents per second. A database restart, which
resets the counter, is not counted.
Both are rows in Admin Panel / Problems / Database with what to do;
docs/Databases/MongoDB/Storage-Requirements.md lists them.
tests/databaseHealthScans.test.cjs drives the log parsing and the rate,
including that a filter's values never reach the row. From the reporter's
patch.
Thanks to Raymond-Voogt and xet7 !
Fixes#6826.
Both were Meteor.setInterval jobs, so every WeKan process behind one
database ran them. On a 24-pod installation (54,234 History rows, 148,660
board memberships, 1,760 position-history entries):
- the change history integrity audit read the whole History 24 times an
hour, and
- UserPositionHistory.cleanup ran 24 times a day, each run one query per
board membership of every user, each fetching up to 1000 whole
documents, to trim nothing - outside the CPU governor.
Both are SyncedCron jobs now, the way the other periodic jobs already
are: synced-cron's { intendedAt, name } lock runs each scheduled time on
one pod only, and its running-job check (indexed in #6769) skips a run
while the previous one is still going. No new collection is needed for
it. The audit stays hourly; the cleanup runs at 4:15 am.
The cleanup asks the database which user/board pairs hold more than 1000
non-checkpoint entries (aggregate) and trims only those: the 1000th
newest entry's date with one indexed query, everything older in one
remove. The result is the same as before - the newest 1000 per user and
board are kept, checkpoints never - also for pairs whose user has since
left the board. It waits for a quiet CPU and pauses between pairs.
models/lib/positionHistoryCleanup.js holds the plan with the database
calls passed in; tests/positionHistoryCleanup.test.cjs runs it on an
in-memory history against the old algorithm's result, counts the queries,
and fails if anything in server/ or models/ drives either job from a
per-process timer again. From the reporter's patch, with the claim moved
onto the existing SyncedCron.
Thanks to Raymond-Voogt and xet7 !
Fixes#6821.
The sidebar card search, the link-card popup and the list, swimlane and
board pickers (Board.searchCards / searchLists / searchSwimlanes /
searchBoards, models/lib/cardSearch.js) and the import member search
(UserSearchIndex) compiled the typed term AS a regular expression:
- "C++", "(2026)", "[x" or a case number like "PL0900-2026(1)" threw
"Invalid regular expression" and the search showed nothing;
- "." matched any character and "*" or "?" changed what was searched for.
Every server-side search (global search, publications, boardTextSearch)
already escapes the term and matches it literally. These now do the same,
still case-insensitive, through literalSearchRegex() in cardSearch.js,
which the user search reuses rather than carrying its own escape. The
Advanced Filter's /regex/ syntax is a separate path and is unchanged.
tests/noUnescapedDynamicRegExp.test.cjs no longer needs its reviewed
exceptions for models/boards.js, models/lib/cardSearch.js and
models/users.js; tests/searchLiteralTerm.test.cjs drives the matching and
fails on any model, client or imports file that compiles a raw term into
a RegExp. From the reporter's patch.
Thanks to Raymond-Voogt and xet7 !
Fixes#6822.
A card inserted through the Cards collection gets its "created" activity
from Cards.after.insert. Two paths inserted through the collection AND
called cardCreation() themselves:
- Board View / Version Control, New issue (vcsCreateIssue), and
- POST /api/boards/:boardId/lists/:listId/cards with linkedId
(createLinkedCardFor).
Each such card got two "created" activities, and every watcher two
notifications and two emails. The other explicit callers - REST create,
bulk create, email view, email to card - insert with Cards.direct, where
no hook runs, and are right to record it themselves.
Both extra calls are removed. Outside a method (a REST route, a server
job) the hook has no user, so the REST linked card's activity would have
lost its author: the hook now falls back to the card's own userId, which
createLinkedCardFor sets to the session user. Activities never exclude
their actor from notifications, so the fallback only names an author
where there was none, such as a recurring card's next copy.
tests/noDoubleCreateActivity.test.cjs walks server/ and models/ and fails
on any explicit cardCreation that follows a hooked insert. The
GHSA-6jr3-42jf-vhm5 guard in restApiIdorBatch now pins the linked card's
attribution through createLinkedCardFor and the hook. From the reporter's
patch.
Thanks to Raymond-Voogt and xet7 !
Fixes#6823.
Every global search and card report (server/publications/cards.js) upserts
its search session by { userId, sessionId }, reads it back by the same two
fields, and removed the user's sessions older than a day by
{ userId, modifiedAt }. The sessiondata collection had only its _id index,
so each search scanned every stored session three times.
And only the searching user's OWN old sessions were removed: a user who
never searched again kept theirs forever. Each session holds the pickled
selector of the search, which lists every board the user can see - large
for an administrator. One instance had 4,751 sessions taking 10 GB, the
biggest collection in its database.
- Indexes { userId: 1, sessionId: 1 } and { modifiedAt: 1 }.
- Sessions older than a day are removed for everybody on each search, as
one indexed delete. Paging (next/previous) works within the day as before.
Test: tests/searchSessionIndexes.test.cjs. From the reporter's patch.
Thanks to Raymond-Voogt and xet7 !
Fixes#6824.
server/cardRecurrenceSchedule.js runs every hour and asks for the cards
with a recurrenceInterval ($exists: true, not null or 'none'). There was no
index on the field, so the query read every card in the database to find
the few that recur. One instance logged it as a slow query: COLLSCAN,
1,108,017 documents examined, 1,155 ms.
A partial index on recurrenceInterval holds only the cards that have one,
the same way activities indexes commentId and attachmentId. The job's
$exists: true lets MongoDB use it; tests/recurrenceIndex.test.cjs pins both.
From the reporter's patch.
Thanks to Raymond-Voogt and xet7 !
Fixes#6825.
Card Settings draws each row once per list, and its minicard list head is
empty; the Date section's card list starts with the fixed Mark as complete
row; the SMTP host field is shown only while email settings are enabled and
SMTP is chosen.
Thanks to xet7 !
Files are served private, no-cache, so every later view of an attachment,
avatar or thumbnail revalidates and gets a 304, and a browser updates the
stored response's headers with the 304's. The app-wide frame-ancestors 'self'
that FrameBleed's restore puts on every response therefore replaced the
file's own policy - an SVG's default-src 'none' and sandbox - from the second
view on. The SVG body is still sanitized on every serve, so this lost a layer
of defence rather than opening script execution. The app-wide framing headers
are now left off a 304, so the stored 200 keeps the policy its route chose; a
header a route set on the 304 itself is kept. Not recorded in Admin Panel /
Problems: it fires on ordinary revalidation, not on an attempt.
Thanks to xet7 !
The theme's quiet text, #6b7280, was 4.51:1 on #f7f7f7 and 4.44:1 on the
tinted card header, at or under the 4.5:1 it needs; #4b5563 is about 7:1.
The opened card and sidebar contrast tests measured right after a reload,
before the board's colour had reached the page, and so measured WeKan's stock
colours; they now wait for the theme class as the header test does.
Thanks to xet7 !
The S3 pane has had two Save buttons since #6465, one under the Enabled and
Read switches and one after the connection fields; the spec matched one.
Thanks to xet7 !
forms.css gives every input the 9px 14px padding of a text field. The
Draggable table brought its native checkboxes back without removing it, so a
16px box left the browser about 2px to draw in and a ticked one was a small
blue dot beside an unticked square. The same was true of the WIP limit groups
popup and of task-list checkboxes in a description. All three have no padding
now, and a test fails on any stylesheet that brings back a native checkbox
with it.
Thanks to mimZD and xet7 !
Fixes#6820.
LinkedWriteBleed over REST is refused and recorded but no longer disables the
account (#6798); notification intents are seeded recent, past the expiry of
#6768; quoted custom field values; the Login and Windows SSO forms each have
a Save; the Bots view under Rules; the popup stub has onCreated and helpers;
the board view panel fills the window first. An invite mail goes to a running
SMTP sink, a sink waits for a busy port, and the allowed AssignedBleed read
retries on a socket reset only. The first checklist is added with the title
line's +.
Thanks to xet7 !
A new drawing was fitted to the popup before it was maximized and kept that
size. It is refitted whenever its room changes, until it is zoomed or resized
by hand.
Thanks to xet7 !
50 ms after an inlined form opens it focuses the field and selects what it
holds, so the first key replaces a title being edited. Text typed or pasted in
those 50 ms was selected too, and the next key - Enter for a new line - left
only a line break. The selection is skipped when the field already changed.
Thanks to xet7 !
The Gregorian option's value is gregorian; the date-changed triggers are on
the card triggers tab; the shared templates and security panes have their own
addresses; Shortcuts opens as a modal; the roles pane has two descriptions;
saved rule descriptions are lowercased; the feature list is FEATURE_KEYS long;
a plain member is not shown the Rules editor at all (#2217), and the server
still refuses its rule writes.
Thanks to xet7 !
A leftover copy of gcs-credentials-description, from before the English and
translated pairs, showed the same sentence twice under the key field.
Thanks to xet7 !
The helper handed TAPi18n.__ over unbound; __ reads this, so it threw and both
descriptions of the visibility popup were an empty line in every language
since the custom descriptions of Admin Panel / Settings / Visibility. It is
passed bound, and a test fails on any TAPi18n.__ passed as a bare value.
Thanks to xet7 !
The popup opens on its Card section and draws only the open section's rows,
each once per list (Show on Card, Show on Minicard). The specs looked for rows
of other sections and matched a row twice. The linked timer spec reloads after
its database writes, so the board's spent time has reached the page.
Thanks to xet7 !
The class that keeps the window unpainted until it is placed beside its
minicard was a static class of the template, removed by hand once placed.
Blaze writes a tag's static classes again whenever its class attribute
changes, so maximizing and minimizing, or collapsing, brought it back and left
the card open but invisible. It is now a reactive class, off for good once the
window is placed.
Thanks to xet7 !
Since #6796 a card with no checklist has only the + of the Checklists title
line (a.add-checklist-top); the bottom a.add-checklist appears once there is
a checklist. The specs looked for the bottom one only, so every spec that adds
a card's first checklist failed, and 12-checklists skipped its checks silently.
Thanks to xet7 !
The scan counts what points at nothing: cards on a missing or another board's
list or swimlane, ghost and duplicate board members, boards with no active
admin, custom field values and boards that no longer exist, checklist items
without a checklist, and subtasks or linked cards whose card is gone. It runs
nightly when the CPU is low, claimed so that only one pod scans or repairs.
A repair that only moves data reuses the upgrade steps; one that deletes asks
twice, and the server refuses it without that confirmation. Every repair edits
with $pull or a guarded update, so a concurrent edit is not overwritten, and a
member dedupe keeps the admin entry. Legacy scalar boardId and string boardIds
of custom fields are understood.
Translations done with low confidence: ay, csb, lld, kok, wa-RR, ve-CC, bi, tpi.
Thanks to Raymond-Voogt and xet7 !
Fixes#6818.
The Corsican and Southern Sotho specs looked for .templates-description and
.shared-templates-hint inside ul#templates-setting, which holds only the three
scope checkboxes; both paragraphs are its siblings in .templates-settings.
Thanks to xet7 !
Found by the Haitian attachment-search browser test. Every attachment
lookup of the global search read the attachment's card from a top-level
cardId, which attachments do not have - they name it in meta.cardId - so
has:attachment, a free-text match on a filename and attachment:<name>
found no card at all; tests/databaseQueryBounds.test.cjs had pinned that
projection. The negated predicates (has:-attachment, has:-checklist)
were ignored and returned the cards WITH one. Soft-deleted attachments
do not count for has:attachment.
Thanks to xet7 !
Stepping to the next result with Enter highlighted it while the last
one stayed lit for the rest of its three seconds, so two cards were
highlighted at once and it was not clear which was current - found by
the #2171 browser test. Revealing a result removes the highlight from
any other.
Thanks to xet7 !
The specs predate an opened card showing only the fields it has data
in: a checklist spec found no Add checklist on an empty card (and five
others skipped their assertions the same way). The seeded board turns
Card Settings / Card's Show empty fields on - what an opened card did
before - and the specs of the empty-field rules (card-add-fields) turn
it off.
Thanks to xet7 !
An opened card shows the fields it has data in, but a linked card keeps
its data on its source card - members, labels, dates, custom fields,
checklists - and the decision read the linked card's own, empty
document: a linked card hid every field of its source. Found by the
#1946/#6613 browser test, whose source member's avatar was missing.
shownFieldsOf() takes the data, and the counts of checklists, subtasks,
text notes, attachments and comments, from the source card, and the
card's own choices (On this card) from the card opened. The opened
card, its sections and Card Settings' On this card all decide through
it. tests/linkedCardShownFields.test.cjs runs it.
Thanks to xet7 !
Found by the #6808 browser test: when an opened card really is deleted,
its window is destroyed with its data - usually before the server has
answered whether the card is gone - and the confirmation was dropped
for a destroyed window, so the window closed but the address stayed
the card's. The question is asked outside the window now
(confirmGoneCard, waiting for a settled connection and login), and the
window's end asks it too when the card is missing; the close
(closeGoneCard) clears the window, the popup and the address. A card
that only left for a reconnect still answers available and stays.
Thanks to brlin-tw and xet7 !
The Retention help text came with Tigrinya's bare ካርድ for card; Tigre
says ወረቀት ካርድ, which tests/tigreAllBoardCardContexts.test.cjs holds.
Thanks to xet7 !
Every Hall of Fame vulnerability without a test naming it (24 names, 30
advisories) was checked against the current source. No fix had been
reverted. Looking for the same mistake elsewhere found siblings:
- FloppyBleed: the fs-path-heal upgrade step joined an attachment's
stored name into candidate paths as-is; a record a client inserted
named ../../../etc/passwd made the next upgrade copy that file into
storage - an arbitrary file read. Only the name's last component is
used now; the bulk move limits a legacy name's extension the same way.
- FilePathBleed (GHSA-g6vm-7757-pr88): the insert guard looked only
inside versions; with versions left out ostrio:files falls back to a
top-level path, which removing the record deletes and validation
reads. A client's top-level path is refused for attachments and
avatars, recorded by the existing canaries.
- MoveStorageBleed: validateAttachment, which deletes a file failing the
current upload rules, was open to anybody who could see the board, a
public one included. Only who may change the file, or a site admin,
validates now; a refusal is recorded (authz.attachment-validate).
- HookUrlBleed / IntegrationBleed: the REST reads of a board's
integrations gave the webhook URL - whose path is its secret - to
every member reading the board; non-admins get what the publication
gives them.
- AttachmentMigrationBleed: four migration methods answered any
logged-in user about any board, deleteDuplicateEmptyLists.getStatus
with a private board's list titles; they require a visible board and
record refusals (authz.migration-board).
- LegacyAttachBleed (GHSA-xcxp-hx9w-q5q9) and MimeStorageBleed
(GHSA-rv5g-5p4g-3v89): the legacy route judged a name dangerous only
for .svg, the response policy missed x-javascript, ecmascript, xsl and
+xml types, and the sniffers missed <tag/onx= handlers.
OIDCBleed refusals are now recorded in Admin Panel / Problems too. New
suites name the vulnerabilities they guard, existing ones name the
advisories they already covered, and tests/securityRegressionCoverage
counts 128 of 137 published vulnerabilities with a named test (was 125).
Thanks to xet7 !
The Retention page (#6789) showed only the variable names
(ATTACHMENTS_RETENTION_DAYS, ...), the raw choices uploaded and
card-inactive, and Default () with nothing between the brackets
whenever the variable was set but empty - which is what a Helm chart or
a docker-compose file listing the variable does.
Catalog fields (models/lib/authConfigCatalog.js) can name a labelKey, a
helpKey and choiceLabelKeys: the form shows the translated label with
the variable name beside it, a help line, and the choices by name.
Fields without them look as before. An empty variable is unset for the
value shown, as the retention code itself reads it, so the default is
shown again; yes/no defaults are translated. The five retention fields
use them; seven new strings in every language (low confidence for the
rarest and constructed ones). From the reporter's patch.
Thanks to Raymond-Voogt and xet7 !
Fixes#6819.
#6817: on v12.65 the reporter ticked Due in Board Settings / Card's
Show on Card and an opened card without a due date showed no Due field.
Show on Card says what a card can show; since v12.65 an opened card
shows a field when it has data. Showing an empty field is done since
#6806 (Upcoming) by Card Settings / Card's Show empty fields, the Show
empty column, or + Add / On this card. The test pins the reporter's
board through each, and that Show on Card off still wins.
Thanks to mimZD and xet7 !
Fixes#6817.
StampBleed (found reviewing the patch for #6813): checklists, checklist
items and text notes carry a denormalized boardId, by which the board
publication publishes them, and items a checklistId, by which a
checklist draws them. The insert allow rules check write access on the
document's card only, and the server's insert hooks kept a boardId the
client sent. So a member of any board could insert, on their own card,
a document stamped with a private board's id - published to that
board's members - and an item with that board's checklist id, drawn in
it. 4bd6a7f088 made the server take boardId from the card; now the
forged insert is refused outright and recorded, for those three and for
a direct attachment insert with a foreign meta.boardId. WeKan's own
client never sends a mismatch.
UploadBleed: Meteor-Files' upload pipeline (_FilesCollectionStart_
attachments / Write and its HTTP twin) inserts the stored attachment
SERVER-side, so the allow rules were never asked, and the attachments'
onBeforeUpload only cleaned the file name: any DDP client, signed in or
not, could put a file on any card of any board - published to its
members with an addAttachment activity and its notifications - and the
admin's uploads-blocked switch was skipped too. The upload is
authorized on every chunk now: signed in, allowed to edit the card (or
write the board, for a background or map image), uploads not blocked,
and a card attachment's board, list and swimlane are its card's.
Both are recorded in Admin Panel / Problems (authz.card-child-stamp,
authz.attachment-upload), once per upload. tests/stampBleed.test.cjs and
tests/uploadBleed.test.cjs run the real decisions, and fail on a card
child with no insert board check or a FilesCollection without a server
upload check; upload-stamp-bleed.e2e.js attacks the running server.
Thanks to xet7 !
The new texts of f89c8f8030 took Tigrinya's word for name (ስም) in Tigre,
which has its own (ስሜት), and repeated Tsonga's nhloko-mhaka (title)
three times in one sentence - the shape the guards against generated
filler catch. tests/tigreAllEstablishedNounContexts.test.cjs and
tests/wrongLanguageCorrections20261014.test.cjs pass again.
Thanks to xet7 !
#5874 fixed new cross-board moves (server/lib/cardBoardConsistency.js),
but a card moved before that fix can still point at a list of the board
it LEFT. Every view of its own board selects cards by that board's
lists, so the card is shown nowhere, and swimlane-structure's
dangling-listId rescue does not see it because the list exists. One
production database (1.1 million cards) had 706 such cards on 383
boards, 391 not archived.
A startup upgrade step, card-list-other-board, gives each such card its
own board's first unarchived list (and that list's swimlane when it is
bound to one), or a Rescued Data list when the board has none. Cards are
streamed with two fields, one updateMany per (list, board) pair,
idempotent. From the reporter's patch, with one change: the step runs
BEFORE swimlane-structure, so a rescued card whose new list is not bound
to a swimlane - it kept the swimlane of the board it left - is put on a
swimlane of its board in the same pass, not hidden in the Swimlanes view
until the next release re-runs the checks.
Thanks to Raymond-Voogt and xet7 !
Fixes#6814.
#5813 / #4743 made server-side card numbers atomic with a per-board
counter, but a card added in the browser - the add-card form, a card
from a template, a link, a checklist item turned into a card - still
brings the number the browser computed: the highest among the cards it
has, plus one. With lazy card loading the browser has only the cards in
view, so it reissued numbers other cards already carried, and the
counter never saw them, so the server reissued them as well. One
production database had 842 numbers used by two or more open cards.
Cards.before.insert on the server: a number another card on the board
already has is replaced from the counter, after raising the counter to
the board's highest number; a free number the counter did not issue
raises the counter to it. A board with no counter yet is seeded from its
highest number first, as getNextCardNumber's lazy seed does - the
reporter's patch created it from the browser's number alone, which can
be below numbers outside that browser's view, and the seed would then
never run. 0 / missing stays no number. Existing duplicates are not
renumbered: a number may be written down elsewhere.
Thanks to Raymond-Voogt and xet7 !
Fixes#6815.
Every way to delete a checklist - the card's checklist menu, DELETE
/api/boards/:boardId/cards/:cardId/checklists/:checklistId, the
removeChecklist rule action and the durable rule plans - removed only the
checklist document; Checklists.before.remove writes the removeChecklist
activity and nothing else. Every item stayed in the database with a
checklistId pointing nowhere: invisible on the card and never cleaned
up. One production database had 2335 of them from 690 deleted
checklists.
Checklists.after.remove now removes the checklist's items with
ChecklistItems.direct.removeAsync, as deleting a card already removes
its children: no activity per item, the checklist's own removeChecklist
activity records the deletion. Items orphaned before this are left for
the administrator to decide. From the reporter's patch.
Thanks to Raymond-Voogt and xet7 !
Fixes#6816.
Since #3986 a new card's title starting with [Text] followed by more
text lost the prefix and got the label Text, created on the board when
it had none - for everybody, without a hint. Titles pasted from ticket
systems, mails and monitoring tools ([Follower] #123456 - ...,
[EXTERNAL] ...) were changed and filled the board with unwanted labels.
It is a Member Settings switch now, Turn [label] at the start of a new
card title into a label (profile.titleBracketLabel), off by default: a
title is kept as typed. Who wants #3986's behaviour turns it on. Both
texts are translated in every language; low confidence for the
constructed and rarest ones (tlh, vo and some 35 others).
Thanks to jullbo and xet7 !
Fixes#6810.
A date popup of a card without that date opens the calendar on today,
marks today (#6751) and names it in the calendar's heading - so today
is what the form shows. But the form's date field was empty, and Save
without clicking a day answered Invalid date.
The form starts from today now (draftDate in
imports/lib/datePickerTime.js): the date field, the calendar's selected
day and its heading agree, and Save saves today at the popup's default
time (17:00 for a due date, #1502). A card's own date is kept as before,
and an emptied date field is still refused.
Thanks to brlin-tw and xet7 !
Fixes#6809.
Dragging a checklist item within its card sets cardId and checklistId
to where the item already is. The ChecklistBleed deny rule
(GHSA-gv8h-5p3p-6hx7) checked only that the destination board was
writable, so for a comment-only member or a Worker - whose own board is
one they cannot write to - an ordinary drag tripped the high-severity
canary checklist-item.cross-board-move and named them in Admin Panel /
Problems / Security as if they had attacked another board.
The board the document is already on is no destination now. That board
is the one of its SOURCE CARD, which the allow rule decides on - not
the document's own denormalized boardId, as the reporter's patch read
it: the insert hooks kept a boardId the client sent, so an item on the
attacker's card could carry a victim board's id and would have passed
as staying while moved onto a victim card. Those hooks (checklists,
checklist items, and now text notes on the server) set boardId from the
card always. A move to another unwritable board, and an unknown
destination, are still denied and still reported.
Thanks to Raymond-Voogt and xet7 !
Fixes#6813.
The fixture's user has an id, not an _id: setHandles(user._id, ...) of
8224852de1 matched no user, so the handle half of the spec never had
drag handles on.
Thanks to xet7 !
Custom fields on minicards became a Board Settings / Card switch,
allowsCustomFieldsOnMinicard (1d4ffd4080), opt-in, with false written
into every board. Before it, a custom field marked Show on card was
always drawn on the minicard, so after the upgrade those fields
disappeared from every minicard until a board admin found the switch -
reported as custom fields being visible only after opening the card.
A startup upgrade step, custom-fields-minicard-on, turns the switch on
for each board that has at least one custom field with showOnCard: that
board's own earlier choice. Boards without one keep the opt-in default.
Once ever, like cover-on-card-on: it records itself in _wekan_migration,
so a board turned off afterwards stays off on later upgrades. The test
db's distinct() returns array elements one by one, as MongoDB does.
From the reporter's patch.
Thanks to Raymond-Voogt and xet7 !
Fixes#6812.
Until the minicard's sections were rewritten (1d4ffd4080), a minicard
drew a custom field marked Show on card only when it had a value - the
2019 check of e2a374f0aa. The rewrite dropped it, so every field a card
carries was drawn, also the ones added empty by Automatically on card /
Always on card: boards with several such fields got a stack of empty
rows on every minicard.
hasCustomFieldValue() in models/lib/customFieldsWD.js: null, undefined,
an empty or blank string and an empty multi-select are empty; 0 and an
explicitly unticked checkbox are values (the old check hid 0). The
minicard draws a field only when it has one; the opened card is
unchanged. From the reporter's patch.
Thanks to Raymond-Voogt and xet7 !
Fixes#6811.
Behind a reverse proxy the DDP connection can drop and come back while a
card's link is still loading (a WebSocket upgrade the proxy refuses,
SockJS falling back). Every subscription re-runs on the way back, one
answers before the resumed login with nothing, and the card leaves
Minimongo for a moment. #3114's close-on-removal took that moment for a
deletion: it closed the card and went to the board's address.
The window now waits while the subscription, the connection or the login
is settling, and when the card looks gone with everything settled, asks
the server (cards.openCardAvailable, the 'card' publication's own rule,
now one function in server/lib/openCardAccess.js) and closes only on its
no, if Minimongo still agrees. A card really deleted, archived or moved
elsewhere still closes. Not reproduced here: no proxy in this
environment; the browser test drops and restores the connection.
Thanks to brlin-tw and xet7 !
Fixes#6808.
On 2026-10-25 each row became one line with both sides' cells and order
arrows. But the lines could follow only one side's order, and the
number beside an arrow was the field's place among ALL of that side's
fields: an arrow often changed the number and nothing visibly moved,
the minicard's arrows never moved a line, and an arrow that did nothing
was not disabled. Drag and drop, which the arrows had replaced, had been
there since 2026-10-02.
Each section has the two lists again, each in its own order and
reordered by drag and drop: Show on Card - with On this card and Show
empty beside each row - and Show on Minicard. A row is dragged by its
handle (drag handles on) or its icon or name; Up and Down on a focused
one move it one row in the list. A dropped row goes right after the row
now above it in the side's whole order, through placeKey, so a pinned
header stays first and a section moves as one. The arrows and their
numbers are gone.
Thanks to xet7 !
Ask for a reason when moving cards was a Font Awesome square a few
pixels off Show empty fields above it, its classes toggled by hand. It
is the same animated checkbox now, in the same place, read reactively
from the board and set by a board admin. Select all and Select none
under the columns' headings were links; they are the Draggable table's
buttons - white text on the theme's colour - in its container.
Thanks to xet7 !
Under each column heading of every pane - On this card, Show on
Minicard, Show on Card, Show empty - Select all and Select none set
that column for the open section's rows at once, each the way its own
checkbox would: On this card in one write of the card, Show empty in
one write of the board's list, the Scrum rows in one Scrum write (one
by one, each would carry the same revision), the others through their
own checkbox's handler. Who may change the column gets the buttons; a
personal row (Labels text, List title) is left as it is.
The headings drifted left of their cells, further each column, and the
labels were cut: the heading line had the smaller font, and the columns
are in em, so its columns were narrower than the rows'. The font is on
the headings now, every heading and cell starts at its column's start,
and the popup is up to 1200px wide instead of 900px, so a row's whole
label fits where the window has the room.
Thanks to xet7 !
Card Settings opens on its topmost entry, Card - also from + Add,
which opened on the first section of fields. Under the Card pane's
title, Show empty fields (the board's showEmptyCardFields, off by
default): ticked, every card of the board shows all its fields, the
empty ones too, and the Show empty columns are hidden - by CSS only, so
their ticks stay saved and come back as they were when it is unticked.
On this card still hides a field from one card, and Show on Card off
still hides it from all.
The Always show column is called Show empty; it is stored as before, in
cardFieldsAlwaysShown. Both headings are translated in every language.
Thanks to jullbo and xet7 !
Fixes#6806.
Show when empty became Always show and covers every per-card field,
the description, attachments and comments included: those were always
shown by a fixed list, and are now the default ticks of the board's
cardFieldsAlwaysShown. A board that never set it shows them as before;
unticked, they are shown when the card has a description, an
attachment or a comment, like any other field. The board's list is
exactly what is ticked once set, an empty list too. On this card still
hides an always-shown field from one card.
The first name, cardFieldsShownEmpty, was never pushed, so there is
nothing to migrate. tests/cardFieldsAlwaysShown6806.test.cjs pins the
default, unticking the four, the storage and the column; the browser
test unticks Comments.
Thanks to jullbo and xet7 !
Fixes#6806.
Since an opened card shows only the fields it has data in, a board where
nearly every card gets labels, a due date and a checklist needed + Add,
the section and On this card for each new card before a field could be
filled in. The new Show when empty column of Board Settings / Card (and
of + Add, the same popup) is the board's list, cardFieldsShownEmpty: a
ticked field counts like data and is on every card from the start. On
this card still hides it from one card, Show on Card off still hides it
from all, and the four always-shown fields have no such checkbox.
It is a board admin's setting, stored normalised by
setCardFieldShownEmpty, and translated in every language.
tests/cardFieldsShownEmpty6806.test.cjs pins the rule, its storage, the
column, and fails on any caller of isShown or cardFieldsAfter that does
not pass the board; card-add-fields.e2e.js ticks it in the browser.
Thanks to jullbo and xet7 !
Fixes#6806.
It read from the LAST comment in minicard.css and cardDetails.css to the
end of the file, so it passed only while the heading rules' neighbours
happened to set no colour - and failed once the Card kind check mark,
appended at the end of cardDetails.css with its own colour, came after
them. It reads the heading block, from its comment to the blank line
after it, and still fails on a colour there.
Thanks to xet7 !
A check mark at the end of its line, bold, and aria-checked in a radiogroup.
Before, the selected kind only had an is-active class that nothing styled.
Thanks to xet7 !
The comment form keeps 38px free for the writer's avatar, which is drawn
at the reading start (inset-inline-start: -38px) - but the room was a
physical LEFT margin, so on a right-to-left page (Persian in the report)
the avatar was drawn on the right, outside the card. The room is
margin-inline now, and the other margins of the comments' layout are
logical too, so all of it mirrors with the page.
tests/commentFormRtl6805.test.cjs pins the avatar to the room kept for it
and fails on any left/right-only margin or padding in comments.css.
Thanks to mimZD and xet7 !
Fixes#6805.
Under Edit custom fields, the Custom Fields pane has the two forms its
buttons opened: Create Field (also from Board Settings, for a board
admin) and Link fields to another card. In the pane the list's own two
buttons are not repeated; a new field saved there empties the form for
the next one, and Card Settings stays open. Opened as their own popup -
the card's Custom Fields hamburger - they step back when saved, as
before (client/lib/popupBack.js, now for a form drawn by more than one
popup, and telling whether it stepped back).
Thanks to xet7 !
Each line of a section pane is its own grid, and its side columns were
sized by their content, so no two lines - nor the heading line - agreed.
They have fixed widths now (On this card 7em, Show on Minicard and Show
on Card 9em each), a longer heading wraps in its column. The inline forms
float their Save left and Delete right (Change spent time, ...); the form
keeps those floats inside it (flow-root), where they had pushed the
heading and the first row aside.
Thanks to xet7 !
- The first entry is Card, with the card icon: Ask for a reason when
moving cards, the Draggable table, and the card's own rows that were
the last entry (Card number, Collapse, Parent card, ...).
- Subtasks is its own section under Checklists, Planning Poker its own
under Voting.
- Each section's settings are the form itself under the pane's title,
not a link to a popup: Subtask settings, Edit custom fields, Change
spent time, Card kind, Edit voting, Edit Planning Poker. Saved in the
pane Card Settings stays open; the section hamburgers of the opened card
still open the same forms as their own popup, which step back when saved
(client/lib/popupBack.js).
Thanks to xet7 !
- The opened card's actions menu is one list, in this order: Move Card,
Copy Card, Save as Template, Copy Template to Many Cards, Card
recurrence, Move to Top, Move to Bottom, Watch, Star | Set color,
Drawing | Copy link to clipboard, Link to board, QR code, History,
Export, Import, Move Card to Archive, More. Each item keeps the
permission it had; the two copies of the menu for the two kinds of
member are one list (cardMenuWrites). tests/cardActionsOrder.test.cjs.
- Card Settings: Time is under Date in the left menu.
- Card Settings / Voting: Edit voting and Edit Planning Poker are the forms
themselves, under the pane's title; saved there, Card Settings stays
open, saved in their own popup (the section hamburgers) they step back.
- Select all / Unselect all are centred under their Draggable heading.
Thanks to xet7 !
- The five leave the card's actions menu. On the opened card each is in
the hamburger of its section: Subtasks (Filter: Subtasks, and the
subtask settings), Voting, Planning Poker, Spent time, and a new Scrum
settings heading over the Scrum fields the card shows (Card kind).
Voting, Planning Poker and Spent time show their heading when ticked
"On this card" before there is anything in them, so the hamburger can
fill them in.
- Card Settings: everything in a section's hamburger is also at the top
of that section's pane, under its title - the card's actions when it
is opened from a card, Subtask settings also from Board Settings.
tests/cardSectionActions.test.cjs fails for a hamburger that is not.
- Filter: Subtasks is one function, client/lib/filterSubtasks.js.
- Card Settings: no select-all rows for Show on Minicard and Show on Card
in its first entry, where there is nothing under them; the popup is
high enough for every entry of its menu.
Docs: docs/Features/Cards/Card-Fields.md.
Thanks to xet7 !
- Board Settings / Card is the Admin Panel's left menu and pane. The menu
lists the sections (models/lib/cardSettingsSections.js): Card Settings
first (Ask for a reason when moving cards, the select-all of each side,
Draggable), then Date, Members, Labels, Description, Checklists,
Attachments, Time, Custom Fields, Voting, Scrum settings, Comments and
Card. Every row of the table is in exactly one section.
- The pane shows the open section's rows, one line each, in the reading
order On this card, Show on Minicard, Show on Card, the field: each side
cell the board-wide checkbox, the field's position on that side and its
up and down arrows (the Up and Down keys on a focused arrow too; the
maintainer's choice, replacing the drag and drop of the two long lists).
Every checkbox is the same animated one, and only the one just clicked
animates.
- The opened card's "+ Add" opens this same popup for the card, with the
"On this card" column; the separate Add popup is gone. A reader who may
not change the board sees its checkboxes disabled.
- A row's name leaves out what its section says: under Scrum settings the
rows are Sprint, Release, ..., not "Scrum settings: Sprint".
- The popup moves by its title bar, maximizes, is 900px wide (popupOffset
and popup.css agree) and high enough for its menu.
- A maximized card's comments, at the top of the right column, have no
separator line above them.
- New string "On this card" in every language. Low confidence: ace, ak, ay,
bi, bm, bua, chr, csb, dz, ff, fj, gn, gv, iu, kl, ks, kw, lld, nah, nap,
nso, om, pap, rn, rup, rw, sc, scn, se, sm, ss, st, tig, tlh, to, tpi, ts,
ve, ve-PP, vo, wal, wo, wa-RR, zgh, nd, xh, zu, ja-HI.
Docs: docs/Features/Cards/Card-Fields.md and
docs/Features/Board-Sidebar/Board-Settings/Card.md. The Playwright specs of
the old two-list layout are rewritten for the sections and arrows.
Thanks to xet7 !
The comments are their own template, cardCommentsSection, in the left
column of the opened card and, on a maximized card, at the top of the
right one beside the fields; the right column is as wide as the left.
Thanks to xet7 !
- The cover image on the opened card is on by default. The schema default
is true, and the once-ever upgrade step cover-on-card-on turns it on for
the boards created while it was opt-in; a board turned off later stays
off, also on an install that had no board yet.
- A new drawing becomes the card's cover; saving an edit leaves the cover.
- One separator line under "+ Add": the Sort group's gate read the board
itself, not the card's view, and drew its rule over fields the card does
not show.
- Similar cards: asked when the card opens, and the section is there only
when there are some, with how many.
- Custom Fields: only on a card that has a custom field value or ticked
Custom Fields in "+ Add" - which is now how a card with none gets the
section and its menu.
- The "+ Add" popup moves by its title bar (and by the drag handle, shown
only while drag handles are on, for every movable popup), has maximize /
minimize beside its close button, is wide enough for the pane's labels
and high enough for every menu entry, and a pane opened shows its ticks
without animating them - only the clicked one animates.
Thanks to xet7 !
An opened card shows only the fields it has data in, plus its
description, attachments and comments, empty or not, and its cover when
it has one. "+ Add", under the cover, opens a popup with the Admin
Panel's own left menu and pane title: the sections (Date, Members,
Labels, Description, Checklists, Attachments, Time, Custom Fields,
Voting, Scrum settings, Comments, Card) on the left, the open section's
fields on the right - the items of Board Settings / Card - each with a
checkbox. Ticking an empty field shows it on this card; unticking a field
hides it from this card, also when it has data. The card keeps its
choices in shownFields and hiddenFields, which accept only field keys.
Board Settings / Card still decides what a card CAN show: a field turned
off there is on no card and not offered. The opened card and its section
templates get their own currentBoard, the card's view of the board with
the flags of the fields it does not show reading false, so the existing
template checks apply without being edited; the minicard is unchanged.
The design is docs/Features/Cards/Card-Fields.md. Seeded e2e cards show
every field, as the specs written before expect.
Thanks to xet7 !
The custom colors added to the Drawing popup's palette are stored in the
user's profile (profile.drawingColors, through the new setDrawingColors
method), so they follow the user to every browser. The method takes only
#rrggbb codes, each once, at most 16, and refuses a caller who is not
signed in; the schema holds only #rrggbb. Someone drawing on a public board
without signing in keeps them in this browser, as before.
Thanks to xet7 !
The drawing area takes all the room the popup has: a new drawing is as
wide and high as that room, instead of a fixed 800 x 500 in the middle.
A handle at its bottom-right corner (bottom-left right to left) makes it
larger, or smaller without cutting off a line, and the magnifier buttons
zoom it, scrolling when it no longer fits. A drawing keeps its size when
saved; older ones are 800 x 500. Save is above the drawing, at the end of
the tools.
Colors: clicking a color opens a color wheel, a brightness slider and the
HTML color code; custom colors are added to the palette, kept in this
browser; the eyedropper picks from the screen where the browser can
(EyeDropper API) and from the drawing elsewhere. Any #rrggbb is a color,
nothing else, so a color never becomes anything else in the SVG.
Add Card: Drawing is in the row Search / Template / Drawing / More
options, for one card, and opens the new card's drawing on the whole
window, saved as an SVG attachment.
New strings in every language. Low confidence: tlh, vo, la, chr, iu, kl,
nah, ay, qu, gn, zgh, tig, wal, om, bo, dz, bua, sah, cv, csb, szl, hsb,
rup, lld, fur, nap, scn, sc, co, ve-CC, ve-PP, wa, gv, kw, rm, ak, bm, ee,
ff, lg, rn, rw, ss, ts, ve, nso, tn, st, ny, sn, nd, wo, fj, sm, to, bi,
tpi, haw, pap, ks, uz-AR, ace, wa-RR, ary, yi, wuu-Hans.
Thanks to xet7 !
Romanian (ro 158 values, ro-RO 179): about 160 values each were Italian
("Sposta in cima", "rosso", "ti ha inviato un invito"), with some Spanish
("disparador", "carril") and French ("Connexion", "Erreur"); also cedilla
ş ţ replaced with comma-below ș ț.
Standard Moroccan Tamazight (zgh, 344 values): about 100 values were
Arabic and about 230 Kabyle or French in Latin letters; all now in
Tifinagh. Values already in Tifinagh, including reviewed ones with a
Latin product name, are kept as they were.
Urdu (ur, 2389 values): most of the file was English spelled letter by
letter in Urdu script ("منتقل کو توپ" for Move to Top - توپ is a cannon,
"سیزے" for Size) or English word order word for word; rewritten in
natural Urdu, with the stray "اردو:" prefixes removed.
Search keywords in these four locales are one word of letters, as the
query parser reads them; the underscore, space and zero-width non-joiner
forms could not be typed as operators.
Every placeholder matches English. tests/wrongLanguageCorrections20261025
pins each language, the placeholders and the search keywords; the guards
that pinned the old Arabic and Kabyle values say why they changed.
Low confidence: much of zgh (technical and statistics terms), Urdu
technical and Blockly terms, Romanian search keywords (culoar, termen,
verificare, câmppersonalizat).
Thanks to xet7 !
A loop drawn with the Lasso selects every line wholly inside it, and a
grouped line brings its whole group. The selection moves together from
inside its box, stopping at the edge; resizes from the handle at its corner,
line widths to the nearest allowed one; groups and ungroups, the group kept
with the saved drawing; and goes to the front, the back, or one layer up or
down. Undo now steps back through every change, a move or a resize being one
step.
New strings in every language. Low confidence: ace, ak, ay, bm, bo, dz, br,
bua, chr, csb, cv, ee, ff, fj, fur, gn, gv, haw, hsb, ig, iu, kl, ks, kw, lg,
lld, nah, nap, nd, ss, ts, ve, ve-CC, ve-PP, qu, rm, rup, sah, sc, scn, se,
szl, tig, tlh, vo, wa, wa-RR, wal, wo, yi, zgh, uz-AR, ja-HI, om, sm, to, tpi,
bi, mi.
Thanks to xet7 !
A streaming tokenizer (models/lib/svgSanitizer.js) replaces the regular
expressions that read an SVG whole: it removes script, foreignObject,
iframe/object/embed, SMIL set/animate, event handlers, javascript:/vbscript:
URIs after decoding character references, dangerous CSS, DOCTYPE/ENTITY XML
loops and processing instructions, wherever they are and however the file is
split into chunks, with bounded memory. UTF-16 is decoded and checked.
Upload sanitizing streams to a temporary file and renames it; validation
checks the whole SVG instead of rejecting any over its 1 MB scan; serving
pipes through the sanitizer with no 8 MB fallback to an unsanitized
download.
Thanks to xet7 !
#6802 asks for handwriting with a pen, saved as a card attachment, and
started while the card is created:
- Add attachment / Drawing, and Add Card's Drawing button (shown while
the composer adds ONE card: the card is added, opened, and the drawing
starts on it), open a NEW drawing on the whole window - the Drawing
popup maximized, without the card's earlier drawings - saved as an SVG
attachment of the card, its lines kept on the card for editing.
- The eraser takes away every line it touches (measured to the line's
segments, its width included); Clear now has the trash icon.
- Palm rejection: once a pen has drawn, a touch does not, and while one
pointer draws no other starts a line. A failed pointer capture no
longer stops a line from starting.
- A Drawing popup opened from the card menu, which replaces the menu in
the same popup, now opens maximized when it was left maximized.
The openers live in client/lib/newDrawing.js, which touches no template:
importing the popup's own module from attachments.js ran it before
cardDetails.jade had defined the template, and the client did not start.
Eraser is in every language. docs/Features/Cards/Drawing.md describes
it all. tests/cardDrawingPen.test.cjs tests the eraser and palm rejection
as pure functions and pins the new flows;
tests/playwright/specs/card-drawing-pen.e2e.js draws and saves both new
drawings to SVG attachments, erases a line and rejects a palm in a
browser.
Thanks to mimZD and xet7 !
Fixes#6802.
Since JVN#74210258 (stored XSS) an SVG upload was refused and any stored
SVG was served as a download, so an SVG could not be a card's picture.
Every uploaded SVG is already sanitized on the server before it is
stored - no script, event handler, javascript: URI, foreignObject or
DOCTYPE/ENTITY XML loop (models/lib/sanitizeUploadedFile.js) - so the
refusal goes, for attachments and avatars.
Serving it inline is made safe in one place, server/lib/svgResponse.js,
used by every route that sends file bytes (universalFileServer.js for
attachments, avatars and legacy files, avatarServer.js,
legacyAttachments.js and httpStream.js): the WHOLE file is sanitized
again before it is sent, whatever is stored, and sent as image/svg+xml
under a Content-Security-Policy with default-src 'none' and sandbox, no
script, X-Frame-Options DENY and nosniff, so even opened directly as a
page it runs nothing. A download is sanitized too; a range request gets
the whole file; an SVG over 8 MB stays a sandboxed download. A file whose
active markup is removed on the way out is logged to Admin Panel /
Problems like the stream sanitizer's, and one removed at upload trips
the existing sanitize.dangerous-content canary.
tests/svgServing.test.cjs runs the real sanitizer on a crafted SVG
(script, onload, javascript:, DOCTYPE/ENTITY, foreignObject/iframe) and
checks the body and headers, the download, the oversized case, and that
no file-serving route in server/routes or httpStream.js sends bytes
without the SVG path, and that nothing else sends image/svg+xml;
tests/playwright/specs/svg-attachment.e2e.js uploads one, checks what is
served, that it shows in the card without running anything, and makes
it the cover and the board background.
Thanks to xet7 !
The row shows or hides the Drawing popup's Open as whiteboard button, and
is now named after the card menu's Drawing it belongs to, with that
entry's translations (cardDrawingPopup-title) and its pencil icon.
Thanks to mimZD and xet7 !
The card's Drawing popup stayed where it opened. Like the opened card it
is now dragged by its title bar or the drag handle beside the title, and
has the opened card's maximize / minimize button: maximized it fills the
window, and minimizing puts it back where it was dragged or resized to; a
maximized popup is not dragged; the drawing keeps its 800 x 500 shape so
the pen lands under the pointer; a popup left maximized opens maximized
again. The shared popup offers both by name (MOVABLE_POPUPS,
MAXIMIZABLE_POPUPS in popup.js), Board View settings staying movable.
The three new strings, Drag to move, Maximize and Minimize, are in every
language. tests/popupMoveMaximize.test.cjs pins the allow-lists, the
header controls, the restored place, the drawing's shape and the strings
in every locale; tests/playwright/specs/popup-drawing-window.e2e.js drags,
maximizes and minimizes it in a browser.
Thanks to mimZD and xet7 !
CodeQL js/incomplete-sanitization, alerts #593, #594 and #595:
docs/Databases/FerretDB/1/SpeedTest/report.cjs escaped `|` as `\|` in
Markdown table cells but left a backslash alone, so a value ending in a
backslash turned the added one into a literal backslash and its pipe
split the cell. One mdCell() helper escapes the backslash first, then the
pipe, and turns a line break into a space; it is also used for the
database's name and the query's title, which were not escaped at all.
tests/speedTestReportCells.test.cjs checks the escaping, that no value
can add a column to its row, and that nothing in the tree escapes a pipe
without the backslash. The report is generated from local speed test
results at build time, so there is no attempt to record in Admin Panel /
Problems.
Thanks to GitHub CodeQL and xet7 !
The shapes FerretDB's PostgreSQL and MySQL backends now answer in the
database - the visible boards and the due cards of the dueCards
publication, and an $in longer than the 32 values MySQL compares with
JSON functions - with negatives that must stay in Go (an operator in an
$elemMatch, a regex branch, a missing field, a long list of numbers).
SQLite, PostgreSQL, MySQL and MariaDB answer all 115 cases as MongoDB 8.
Thanks to xet7 !
npm audit reported two high severity issues: @azure/msal-node
>= 5.0.0-beta.0, < 7.1.0 may disclose authorization code flow parameters
to an attacker-controlled endpoint, and @azure/identity depends on it.
It reaches WeKan through @tweedegolf/sab-adapter-azure-blob, whose
@azure/identity (4.14.0, the newest, and 4.15.0-beta.1) asks for
msal-node ^6.0.0, so `npm audit fix --force` reported "up to date" and
changed nothing. package.json overrides it to ^7.1.1. Its only breaking
change since 6 removed responseMode from acquireTokenInteractive, which
@azure/identity never passes; DefaultAzureCredential, ManagedIdentity and
ClientSecretCredential construct, and a token request reaches Microsoft
Entra and fails as an authentication error for a made-up tenant. WeKan's
Azure Blob storage uses DefaultAzureCredential, not the authorization
code flow. The lock file also catches up with the installed
@azure/identity 4.14.0. npm audit: 0 vulnerabilities.
tests/msalNodeAdvisory.test.cjs pins the override and that no locked copy
is in the vulnerable range.
Thanks to xet7 !
"Activity notification delivery incomplete; pending intent retained" was
logged for nearly every activity - 1,080 times in one test-server run,
and on a production server one line after another - and never said why:
the .catch() dropped the error. It and the recovery and both cleanup
lines now carry the failure's code (activity-notification-intent-invalid,
sync-busy, ...). Only a code is logged as it is; any other error gives its
name and numeric or short code, never its message, which may carry an
address or a card title. tests/notificationFailureReason.test.cjs pins
that, and fails when a notification failure line in server/ drops its
reason again.
Thanks to xet7 !
Rerun of the four FerretDB v1 backends at twice the production data,
with the MySQL/MariaDB index reads and VIRTUAL index columns, the
multikey side tables for MySQL/MariaDB, the PostgreSQL GIN membership
index and SQLite sorting in the database. MongoDB 6-9 and FerretDB 2
keep their rows from the same directory. Every database returned the
same data as MongoDB.
All Boards count went from 8-9 s to 159 ms on PostgreSQL and from
9.4 s to 1.1 s on MySQL; adding the 44 indexes took 172 s on MySQL and
901 s on MariaDB instead of 20 and 40 minutes. This build is before
the fixes for SQLite's People page (the database sort read its key
three times per row), MySQL/MariaDB All Boards with a starred board, and
Due Cards' $and on PostgreSQL, MySQL and MariaDB, which a rerun now in
progress measures.
Thanks to xet7 !
WeKan's due cards query wraps its board ids in an $and, which FerretDB's
PostgreSQL, MySQL and MariaDB backends now push down branch by branch;
both shapes answer as on MongoDB 8 on every backend.
Thanks to xet7 !
At start it removed every wekan-conformance-db-* container and network as
left behind by an interrupted run, including those of a run still in
progress: a speed test beside a conformance run lost its MySQL in the
middle of Due Cards ("connection refused") and its MariaDB while
loading, and both results were lost. Containers and networks now carry
the process id of their run as a label, and one whose run is alive (a
db-conformance.sh process) is kept; one without the label, of a run that
ended, or whose pid now belongs to something else is removed as before.
tests/dbConformanceStaleContainers.test.cjs runs the script's functions
against a fake docker.
Thanks to xet7 !
Three cases for what FerretDB now answers from indexes or in the
database: an equality or $in on an indexed field (inside an array, a
value differing only by case, special characters, numbers, a long
string), membership in an indexed array of documents in every shape the
array can take and after writes, and a sort over every simple type with
ties, null and missing fields, with and without a limit, then with an
array and a document among the values. SQLite, PostgreSQL, MySQL and
MariaDB answer all 114 cases as MongoDB 8 does.
Thanks to xet7 !
import-too-large used the provisional ፈይላት, which
tests/tigreCorpusAttestedPlurals.test.cjs rejects in favour of the
corpus-attested ፋይላት used everywhere else in the Tigre file.
Thanks to xet7 !
tests/importPage.test.cjs still required the checked box's 17px label gap
to be margin-inline-end and no margin-right anywhere, which #6786 changed
on purpose: the tick is placed with left/margin-right so it stays on its
square right-to-left, and a [dir="rtl"] rule moves the label gap to the
left. The guard now pins that, and the unchecked box's logical gap; the
comments in import.css name the tick's physical offsets.
Thanks to mimZD and xet7 !
The resize handle is on the list's inline end, its left edge in a
right-to-left language, but the width was the start width plus the
pointer's movement: dragging the handle out of the list narrowed it,
and dragging it in widened it. The movement is now counted with the
list's own direction, as the board sidebar and the left menus already
do. tests/rtlListResize.test.cjs pins it and requires every width
resizer to flip right-to-left; a Playwright spec drags the handle out
and back in, in both directions.
Thanks to mimZD and xet7 !
Fixes#6801.
#6786's first fix drew the tick with physical borders, so it is a check mark
and not "<" right-to-left. But where it sits was still logical: the tick is a
box narrowed from 13px to 7px and rotated about its bottom-right corner, placed
with inset-inline-start: -3px and margin-inline-end: 6px. A right-to-left page
started the narrowed box at the square's RIGHT edge and pushed it further
right, so the tick sat beside its box - "out of box" in the issue's Admin Panel
screenshots. Measured in Chromium, the right-to-left tick spanned 303-323px
beside a square at 291-308px.
The shared tick, the Admin Panel's, the sidebar's and the Clean themes' are
placed with left and margin-right now: 291-311px, on the square, at the same
place as left-to-right, and 7 + 4 + 6 = 17px keeps the label where it was. The
import page, whose rows add a gap before the label, keeps that gap on the
reading side with a right-to-left rule. Toggle switches and the board list's
corner box, which are not this tick, still mirror.
tests/rtlCheckMark.test.cjs fails if any rule that draws a tick places it with
a logical offset or margin; tests/rtl.test.js allows physical margins and a
left offset only inside checked-state rules; the Playwright spec measures that
the tick is on its square in both directions and the label does not move.
Thanks to mimZD and xet7 !
Fixes#6786.
docs/Databases/FerretDB/1/SpeedTest/speedtest.sh (build.sh and build.bat ->
Tests -> Database speed test) is releases/db-conformance.sh with
WEKAN_DB_SUITE=speedtest: the same FerretDB built from source and the same
databases started the same way - SQLite, PostgreSQL, MySQL and MariaDB through
FerretDB v1, MongoDB 6, 7, 8 and 9, and now FerretDB 2 with PostgreSQL and
DocumentDB.
data.cjs generates, deterministically, twice the users, boards, lists,
swimlanes and cards of a production instance (615,070 cards), skewed the way
real ones are; indexes.cjs creates the indexes WeKan creates, read from its
ensureIndex calls; queries.cjs asks 18 common WeKan queries as WeKan's own code
builds them; report.cjs writes Results.md - a row per database, fastest at the
top, a column per query - and fails when a database answered or stored
anything differently from MongoDB. README.md describes it, what it found, what
was improved and what could still be.
db-conformance.sh: WEKAN_DB_BACKENDS runs only the named databases (to repeat
one into an earlier run's WEKAN_LOGDIR), containers are removed with their
volumes, SQLite's data is under .tools/tmp, and FerretDB 2 is a backend of the
speed test, and of the conformance run with WEKAN_CONFORMANCE_FERRETDB2=1. The
conformance catalogue gains a case of writes by string _id and _ids differing
only by case, which found that FerretDB on MariaDB 12.3 lost them.
tests/dbSpeedTest.test.cjs checks the data, the index list, the queries, the
report's order and that a difference fails it, the wiring and the Due Cards
board scope.
Thanks to xet7 !
ghcr.io/ferretdb/postgres-documentdb:17 installs pg_cron, which may only be
created in the database cron.database_name names - postgres. With POSTGRES_DB
ferretdb the DocumentDB extension failed to install and the server never
started. POSTGRES_DB and FerretDB's URL use postgres now.
Thanks to xet7 !
The publication fetched every board the user may see - every public board of
the instance among them, whole - to read its _id and members. It asks for those
two fields only. And with no assigned-only board, the usual case, its board
scope is { boardId: { $in } } instead of an $or with an empty second branch,
which MongoDB and FerretDB both answer from the { boardId, archived, type,
dueAt } index. A board where the user is assigned-only still shows only the
user's assigned cards. On the database speed test (615,070 cards) Due Cards on
FerretDB + SQLite went from 34 s to 12.8 s, with FerretDB's own changes.
Thanks to xet7 !
tests/incompleteEscaping.test.cjs refuses a RegExp built from a value that only some metacharacters were escaped in; the guard escaped only '-'.
Thanks to xet7 !
wekan/FerretDB now releases ferretdb-sqlite-<arch> beside ferretdb-<arch>: the
same FerretDB with only the SQLite handler, about 5 MB smaller, and SQLite is
all a WeKan bundle, image, snap or .spk runs. releases/ferretdb-asset.sh is the
one place that chooses: the newest release that has ferretdb-sqlite-<arch>,
and, until one does, the full ferretdb-<arch> of the newest release, which
has the SQLite handler too. It says in the log which one it took and why,
downloads it, verifies it against its .sha256sum, and returns FERRET_ASSET,
FERRET_TAG, FERRET_URL and FERRET_SHA for the provenance table (also through
$GITHUB_ENV, cached per job so the check and embed steps agree).
Every FerretDB download goes through it: the amd64, arm64, win64, win-arm64,
win32, mac-arm64, mac-x64 and extra-architecture jobs of release-all.yml (the
Docker images, snap, AppImage, Flatpak, single-file and macOS/Windows
packages are made from those bundles), repack-bundle-for-arch.sh for
release-all-missing.yml, bsd.yml, check-arch-binaries.sh,
build-release-bundle.sh and test-download-urls.sh. require-binaries.sh checks
the resume-login floor against the release the helper chose. The provenance
rows record that release and URL instead of /latest/.
No release workflow builds FerretDB: sandstorm-src/build-deps.sh takes the
prebuilt ferretdb-sqlite-amd64 by default (FERRETDB_SOURCE=download), and
sandstorm.yml and release-all.yml's build-sandstorm no longer set
FERRETDB_SOURCE=build; building from source stays a local option. The default
docker-compose.yml downloads ferretdb-sqlite-<arch> (FERRETDB_BINARY, falling
back to ferretdb-<arch> for a release without it); the PostgreSQL, MySQL,
MariaDB and SAP HANA compose files keep the full binary.
tests/ferretdbSqliteAsset.test.cjs drives the helper against a fake GitHub
(newest, older and no release with the SQLite-only binary, .exe names, a
pinned tag, FERRETDB_VARIANT=full, checksum match and mismatch, a missing
platform, bad names) and checks that every download site uses it, that no
workflow builds FerretDB and that only the SQLite compose file takes the
SQLite-only binary. The tests that pinned the old URLs follow the helper.
Thanks to xet7 !
A grain has one login (Sandstorm's), one database (FerretDB on SQLite) and one
mailbox (the grain's own address), but the .spk carried every login package
WeKan has and a FerretDB with the PostgreSQL, MySQL and SAP HANA handlers.
releases/sandstorm-slim-spk.sh, used by sandstorm.yml, release-all.yml's
build-sandstorm job and build.sh's "Make the .spk package", builds with the
package profile of releases/sandstorm-slim.mjs - accounts-password, 2FA,
passwordless, lockout, OAuth2/OIDC, CAS, LDAP, SAML and the seven social
logins removed from a copy of .meteor/packages for that one build, then
restored - trims the bundle for a linux/x64 grain, verifies none of those
packages got in and packs with spk pack (meteor-spk pack rebuilds and would
undo the trim). sandstorm-src/build-deps.sh builds FerretDB from source with
ferretdb_no_postgresql,ferretdb_no_mysql.
Under Sandstorm the Admin Panel hides the E-mail, Login, Locked users, SAML,
LDAP, OAuth2, CAS, Header login, OAuth providers and Passwordless panes,
Attachments / Database migration, the Email-to-card IMAP and webhook settings,
the New user button and the password fields; a hidden pane opened by URL lands
on the first visible one. POST /users/login and /users/register answer 404
sandstorm-sso-only, database migration is refused and the IMAP/webhook
settings are not stored. Without accounts-password the server creates
Accounts.emailTemplates with the package's defaults and marks useraccounts
initialised, which otherwise stopped the first grain.
Against wekan-12.63-sandstorm.spk, built from the same source: 181,307,364
instead of 185,271,476 bytes (-3.8 MiB, -2.1 %), unpacked 26.7 MiB smaller.
Docker, Snap and the bundles build from the full package list, and a test fails
if that list is slimmed. Tested on a local Sandstorm 0.308: a grain starts, a
board, a list and a card are created, and the removed options are not shown.
tests/sandstormSlim.test.cjs and tests/playwright/specs/sandstorm-slim.e2e.js
pin it. Design: docs/Platforms/FOSS/Container/Sandstorm/Smaller-wekan-spk.md.
Thanks to xet7 !
Collection2's attachSchema lives in the package's lazy main module, which only
aldeed:schema-index loads, through an asynchronous Collection2.load(). Nothing
waited for it: the models attached their schemas with whichever attachSchema
existed first - collection2's, or the bare shim in imports/collectionHelpers.js,
which stores the schema and never cleans a write. The full build happened to
load collection2 first. A build with fewer packages (the Sandstorm .spk) reached
the models sooner, so a new board's swimlane was stored without archived, sort
and type, and the board opened empty. server/main.js now loads
meteor/aldeed:collection2/main before the models in every build.
Thanks to xet7 !
A card made from a card template, linked from another board, or copied - and
a subtask copied with it - took its whole customFields array along: values of
another board's fields, of deleted fields, and entries with no field id. The
admin-only field guard could not match those to a field of the new card's
board, refused the whole card and recorded AdminFieldBleed against the user.
copy(), its subtask copies and link() now keep only the values of fields
defined on the destination board (models/lib/customFieldsForBoard.cjs), read
from the collection rather than the cache, just before the insert. An
admin-only value of the destination board is kept, so the guard still decides
it. tests/copyForeignCustomFields.test.cjs fails on the code before the fix,
and fails on any card cloned from another and inserted without the filter.
Thanks to Raymond-Voogt and xet7 !
Fixes#6800,
models/lib/databaseErrors.js builds the connection-lost rule's pattern from
CONNECTION_LOST.source (#6799), a constant regex of the same file, so no
caller value is spliced into it. Listed with that reason, as CARD_URL_RE.source
already is.
Thanks to xet7 !
In Kubernetes a restarting MongoDB pod's DNS name does not resolve for a
few seconds, and the driver throws "getaddrinfo ENOTFOUND
wekan-mongodb-0...". No rule in models/lib/databaseErrors.js knew it, so
it was not transient, and server/lib/uptimeWatch.js recorded every
unhandled rejection as a critical CrashBleed although 00processErrors.js
kept WeKan running: nine "crashes" while WeKan reconnected by itself.
connection-lost now matches getaddrinfo ENOTFOUND and EAI_AGAIN, but only
for the DATABASE's own host - from the caller, or from MONGO_URL and
MONGO_OPLOG_URL (every host of a replica set, mongodb+srv too) - so a
webhook or mail host that does not resolve is still not a database
problem. The crash recorder skips what isTransientDatabaseError already
handles, for both uncaught exceptions and unhandled rejections.
tests/databaseDnsRestart.test.cjs parses connection strings, runs both
guards' decision on the database host and on other hosts, and runs the
crash recorder's own listeners: a database coming back records nothing,
a real crash and a foreign DNS failure still do. It fails on the code
before this commit.
Thanks to Raymond-Voogt and xet7 !
Fixes#6799,
Accounts were disabled for members working on their own board; one
instance lost four in three days, each user then refused at login 81
times.
- A Worker may move a card (#3189) and tick a checklist item (#3307),
but canUpdateCard and the checklist item rule asked
canEditCardOrLinkedCard first, which recorded authz.linked-write (high,
blocked) before the Worker carve-out allowed the change, and that
record disables the account. Both now ask without recording first, and
record only a change that is still refused after the carve-outs.
- A read-only or comment-only member whose screen still offered an edit
(a role changed while the board was open) was refused with the same
record. authz.linked-write keeps being recorded and the write keeps
being refused, but it no longer disables the account (blocksAccount:
false, as ssrf.webhook since #6782).
tests/workerNoSelfBlock.test.cjs runs canUpdateCard and the checklist
item rule from their own source: a Worker's move and tick are allowed
with nothing recorded, other refused edits are still recorded, the
catalog entry never blocks through blockOnSecurityEvent's own decision,
and no rule with a Worker carve-out records before it. It fails on the
code before this commit. boardRoleViews follows the two-step check.
Thanks to Raymond-Voogt and xet7 !
Fixes#6798,
Admin Panel / Problems / Security showed HistoryIntegrity (critical)
81,833 times. A list bound to no one swimlane has swimlaneId '', and the
legacy history append hashed the row with '' and then inserted it
through collection2's default clean, which removes empty strings, after
which the schema's defaultValue stored null: every such list move failed
its own checksum at every audit and was reported as tampering. The
stored-chain path already inserted with { removeEmptyStrings: false,
trimStrings: false }; the legacy path now does too.
Rows already stored that way verify: a null where '' was hashed is
accepted in the id and label fields (boardId, swimlaneId, listId, cardId,
group, batchId, restoredFromId, restoredByUserId) and nowhere else, so a
value replaced by null, a different value, or any change to content, user
or entity still fails.
tests/historyEmptyStringChecksum.test.cjs appends through a collection
that cleans like collection2, verifies already-cleaned rows, fails every
real change, and fails on any ChangeHistory insert in the tree that would
clean or trim; it fails on the code before this commit.
Thanks to Raymond-Voogt and xet7 !
Fixes#6797,
#6796: an opened card gave most of its height to controls with nothing in
them. Stickers, Location, Dependencies and List already have Show on Card
rows, and Date Format is in Member Settings; what was left:
- A field's add button sits on its title's line (Labels, Stickers,
Location, the dates, Members, Assignee, Creator, Requested and Assigned
By, Dependencies); what a field holds follows and wraps. Requested and
Assigned By put their two buttons side by side.
- Checklists: the controls under the list share a line, and the bottom
"+" is drawn once there is a checklist; before that it is the title
line's.
- Attachments: the upload rules ("If filename is invalid, ...", size,
types) are in the upload popup, not body text above every list. "+" is
a small button. A file with nothing to preview gets a small file button
that still opens the preview, instead of a 144x96 box with "txt" in it,
and the name has a line of its own, so a long one is not hidden behind
the buttons.
- Two new Board Settings / Card / Show on Card rows, Attachments: Preview
and Attachments: Details (uploader and date), both on by default, built
from existing translations.
Board Settings / Card is a centred panel as wide (up to 1800px) and tall
as the window, and each list spreads into as many 10em columns as fit,
read top to bottom: on 1920x1080 nothing is scrolled, on 1366x768 32px.
Its rows are dragged by the handle, the icon or the label when drag
handles are on, and by the icon or the label, with no handle drawn, when
they are off. With handles on only the handle dragged, and the rows were
locked to vertical moves, which the columns made unusable.
tests/declutterCard6796.test.cjs runs the tile decision and the popup
geometry, and checks the gates, the CSS and the drag wiring;
tests/playwright/specs/declutter-card-6796.e2e.js checks the add buttons
on their title's line, the compact attachment, the rules in the upload
popup, Attachments: Details, the new rows saving, no scroll at 1920x1080,
and a drag by label and handle with handles on and by label with them
off. cardSettingsCoverage and narrowWindowLayout follow the new rules.
Thanks to mueschel and xet7 !
Fixes#6796,
It escaped only . and $ when building a pattern from an identifier, which
tests/incompleteEscaping.test.cjs reports anywhere in the tree.
Thanks to xet7 !
While MongoDB restarted, Admin Panel / Problems showed
"Exception while invoking method 'usersHeartbeat' ValidationError:
Failed validation Socket 'connect' timed out" as a schema bug, telling
the admin to look for an empty required field and report a WeKan bug.
collection2 wraps whatever failed during a write's validation in
"ValidationError: Failed validation", an unreachable database included;
the schema rule came first, and the driver's connect timeout matched no
rule at all. The same classification decides in 00startupResilience.js
and 00processErrors.js whether an error is transient, so it was fatal at
startup too.
schema-validation now yields when the same message carries a lost
connection, a timeout, a lock or too many connections. connection-lost
also knows the MongoDB driver's own errors: Socket '...' timed out,
MongoNetworkTimeoutError, MongoNetworkError, Server selection timed out,
MongoServerSelectionError, PoolClearedError, NotWritablePrimary and
"not primary" (not SQL's "not primary key").
tests/databaseErrorsWrappedCause.test.cjs runs both guards'
isTransientDatabaseError from their own source on the reported error,
puts every transient cause behind three wrappers and requires its own
rule each time, and keeps a real schema failure a fatal schema failure.
Thanks to Raymond-Voogt and xet7 !
Fixes#6795,
Admin Panel / Problems showed "Created the missing index "-modifiedAt" on
lists" at every start, from every pod. server/models/users.js passed the
list sort options ('-modifiedAt', 'title', ...) to ensureIndex as
strings: the driver read each as a field name, so lists got indexes on
fields named "-modifiedAt", "-title" and "-sort" (on no document, kept up
to date on every list write), and ensureIndex compared a string's JSON
with an index key object's, which never matched, so all six were
re-created and reported every time.
models/lib/indexKeys.cjs turns the sort options into one single-field
index per field ({ modifiedAt: 1 }, { title: 1 }, { sort: 1 }; one serves
both directions) and finds the stray single-field "-x" indexes, which
startup now drops; a compound index containing such a field is left
alone. ensureIndex turns a string key into the key it means, with a
warning, instead of indexing a field named after it.
tests/listSortIndexes.test.cjs uses the reporter's real index list, runs
ensureIndex itself against a fake collection (a string key creates the
real index once and reports it once), and fails on any ensureIndex call
in the tree whose key is not an index key object.
Thanks to Raymond-Voogt and xet7 !
Fixes#6794,
The Valencian file is ca@valencia.i18n.json, but its registry tag is
ca-valencia, and resolveTag and the browser-language matcher only treated
'_' as '-'. A profile saved as the file's name - which is what
tests/playwright/specs/rules-blocks.e2e.js does - resolved to nothing and
the page stayed in English; that case failed on the commit before the
npm updates too. Both now compare through one normalizeLanguageTag
('_' and '@' as '-'), exported from imports/i18n/browserLanguage.js.
tests/browserLanguageFallback.test.cjs uses that function rather than its
own copy of the old rule, checks ca@valencia, and requires every locale
FILE name to resolve to a registry language.
Thanks to xet7 !
imapflow 2.1.2 -> 2.8.0 and mailparser 3.9.31 -> 3.12.2 (email to card):
bug fixes, and mailparser no longer turns a decoded display name into an
address. pdfjs-dist 6.2.108 -> 6.4.299 (attachment text search),
isomorphic-git 1.42.4 -> 1.44.3 (GitHub wiki import), pretty-ms 9 -> 10
(upload time estimate; its breaking changes are in colonNotation, which
WeKan does not use, and the output it shows is unchanged).
Each now has a test that RUNS it the way WeKan calls it, so the next
update that moves an API fails in seconds:
- imapflowMailparserRuntime: the poller's connect, mailbox lock, unseen
search, fetchOne, simpleParser and \Seen flag against
server/lib/tests/fakeImapServer.js; a wrong password is refused, and an
encoded address in a display name stays a name.
- pdfjsTextRuntime: documentGif.js's legacy build, worker, fonts, CMaps
and getTextContent on a PDF with known text; a non-PDF is refused.
- isomorphicGitRuntime: githubWiki.js's log, recursive readTree and
readBlob on a two-commit repository; a missing object is an error.
npm audit reports nothing. The e2e run against a build with these:
rules-blocks (147 languages), attachments, both email specs, and the
security specs pass, except two rules-blocks cases that fail the same way
on a build of the commit before this one.
Thanks to developers of dependencies and xet7 !
Within their ranges or pins: @aws-sdk/client-s3 and lib-storage 3.1155.0,
@fastify/busboy 3.2.4, the three @tweedegolf/sab-adapter-* 3.0.5/3.0.3,
blockly 13.4.0, dompurify 3.4.17, file-type 22.2.0, filesize 11.0.28,
i18next 26.5.4, meteor-node-stubs 1.2.32 (pbkdf2 is still 3.1.7 after
the postinstall patch), wasmagic 1.0.12, and for development
@rspack/core 2.2.9, @typescript-eslint/* 8.72.0 and puppeteer 25.15.0.
The lockfile is written by npm 12.2.1, the Dockerfile's. npm audit
reports nothing.
Blockly 13.4 adds a search box to the toolbox, with five new messages.
releases/translations/import-blockly.cjs added them to every locale, and
they are translated directly into all 230 locale files, from each file's
own words for "block" and "search"; %1 is kept in every count. The
importer also replaced Telugu's literal blockly-LOGIC_NULL "null" with a
word meaning "invalid"; it is "null" again, as
tests/vietnameseBulgarianCompletion.test.cjs requires. Corrected while
there: Polish "Szukaj blokad" (search for blockades) from Blockly's own
catalog is "Szukaj bloków", Arabic's singular count drops a stray "(1)",
and Odia's "no-results" had English mixed into it.
Low confidence, for a speaker to correct: ace, ak, an, ary, ay, bi, bm,
bo, bua, chr, co, csb, cv, dz, ee, ff, fj, gn, gv, ha, haw, hsb, ig, iu,
kl, kok, ks, kw, la, lg, lld, mg, mi, nah, nap, nd, nso, ny, om, pap, qu,
rn, rup, rw, sah, sc, se, sm, sn, so, ss, st, szl, tig, tlh, to, tpi,
ts, uz-AR, ve, ve-CC (Venetian), ve-PP (Veps), vo, wa, wa-RR (Waray),
wal, wo, xh, zgh and zu.
All 1988 node suites pass except tigreCorpusAttestedPlurals, which
fails on the commit before this one too.
Thanks to developers of dependencies, translators and xet7 !
openbsd-vm 1.4.9 adds OpenBSD 8.0 as the VM's default release and fixes a
non-root user's writes to the NFS share; the BSD bundles keep release 7.8
from releases/bsd/targets.json, so they are still built for 7.8. The three
setup-node@v6 steps in the disabled Playwright workflow are pinned to
v7.1.0 by commit, like the fifteen in the active workflows.
Thanks to xet7 !
snapcraft-core26.yaml still had the caddy part snapcraft.yaml had before
v10.71: the version from api.github.com, which answers 403 to a busy CI
runner and, under snapcraft's pipefail, ended the part before its 2.8.4
fallback; a plain curl with no retry and no fallback for a CPU the newest
Caddy does not publish; and a default Caddyfile that proxied to 3000,
where WeKan does not listen when Caddy is on (it listens on
CADDY_BIND_PORT, 3001). Both snaps build from snap-src, so core26 now has
snapcraft.yaml's part unchanged, and both pin 2.11.7 as the fallback.
tests/releaseSnapArches.test.cjs read snapcraft.yaml only, which is how
the core26 copy was missed; it now checks every snapcraft*.yaml, and the
default Caddyfile's port. With the old core26 file it fails.
Thanks to xet7 !
GitHub CodeQL alert 591 (js/insecure-randomness, CWE-338), in the
dynamic-import fork: the key that lets a request read the SERVER's
dynamic modules (?key= on /__meteor__/dynamic-import/fetch) came from
Math.random(), whose xorshift128+ state can be recovered from earlier
outputs, and was compared with ===. meteor shell's connection key, which
runs code in the server process, was made and compared the same way.
Meteor 3.5.3 and 3.6-rc.0 both have this.
Both forks now use crypto.randomBytes (160 and 128 bits) and compare with
crypto.timingSafeEqual. tests/meteorForkRandomKeys.test.cjs runs both, and
fails on any Math.random() in the forks and on a key, secret, token,
nonce, salt, password or code made from Math.random() anywhere in the
tree. A wrong key is not recorded in Admin Panel / Problems: another
WeKan instance behind the same load balancer legitimately sends its own.
Thanks to GitHub CodeQL and xet7 !
secureTransfer recorded every throw as a high "blocked" event naming the
user, and that disables the account. sanitize-html 2.18.0 threw
RangeError on nested <iframe>, so a card planted with the payload locked
out the next member who exported the board; any library fault would do
the same. The transfer boundary now marks its own refusals
(TransferBoundaryError: a cycle, a non-plain object, a size limit), and
models/lib/transferFailure.cjs records only those as blocked - high, and
medium for a size limit as before. Anything else is ImportExportFailure:
detected, still naming whose import or export it was, and never blocking
an account.
tests/transferFailure.test.cjs drives real boundary refusals and a
sanitizer that throws through blockOnSecurityEvent's own decision; with
the old secureTransfer it fails. It also fails if the boundary throws a
plain Error or anything but secureTransfer calls it.
Thanks to xet7 !
sanitize-html 2.18.0 re-sanitized the fallback of a discarded <iframe>
with no recursion limit (GHSA-c9rx-x947-fp3h, CWE-674). WeKan's server
sanitizer discards every tag, so a few kilobytes of nested <iframe> threw
RangeError in secureTransfer, on every import and export - and
secureTransfer records a throw as a high blocked event naming the user,
which disables that account. A card planted with the payload locked out
the next member who exported the board. 96ed9ad202 moved to 2.18.1.
tests/sanitizeHtmlNestedIframe.test.cjs runs WeKan's own sanitizeInput
options on three nested-iframe payloads, checks the installed and locked
versions, and fails if anything else imports sanitize-html; with 2.18.0
it fails with RangeError. The Playwright spec exports and imports a board
holding the payload and checks that nobody is locked out.
Thanks to xet7 !
Meteor published the same six fixes as 3.3.3, 3.4.2 and 3.5.3 on
2026-10-16. 3.6-rc.0, which WeKan runs, does not have them: its tag is
behind 3.5.3 by exactly the 15 security commits, and neither it nor
Meteor's devel branch has the fixed code. Going back to 3.5.3 would also
mean Node.js 24 and Rspack 1, so WeKan carries the fixes in packages/,
where a package overrides the release's own: 3.6-rc.0's allow-deny,
ddp-server, dynamic-import, facebook-oauth, google-oauth and shell-server
with Meteor's 3.5.3 patch applied unchanged, each marked with a _1 wrap
number. ddp-server leaves out uWebSockets.js, as Meteor's published
package does; it is only for the opt-in uws transport and came from a git
URL.
- DDP: a message named __proto__, toString or not a string, and an async
handler that rejected, crashed the server.
- allow/deny: a top-level __proto__ in an update was missing from the field
list the validators saw.
- dynamic import(): requests are bounded in size (413) and complexity
(400).
- Google and Facebook direct-token login: a token issued to another
application was accepted; identity now comes from the provider, and a
Facebook verification error no longer carries the app secret.
- meteor shell: its history and compile cache are private.
- Deploy discovery is in meteor-tool, which an app cannot replace; WeKan is
never deployed with meteor deploy, and a test keeps it so.
Refused attempts are in Admin Panel / Problems as Meteor DDP, Meteor
allow-deny, Meteor dynamic-import and Meteor OAuth, through a hook the
packages call (server/lib/meteorSecurityHook.js); none blocks an account.
An oversized import() body, an unknown ordinary DDP name and a network
failure are refused without a row, because ordinary use can reach them.
tests/meteor353SecurityFixes.test.cjs runs each fix's code cut out of the
fork and searches the tree for the shape of each fault; all of them fail on
the unfixed 3.6-rc.0 packages. It also fails when .meteor/release moves
on, because a fork would silently keep 3.6-rc.0's code.
tests/playwright/specs/meteor-353-security.e2e.js sends each attack to a
running WeKan built with the forks. The forked files are recorded in the
source audit baseline, and the suites that check WeKan's own methods and
subscriptions skip Meteor's package tests.
Thanks to Meteor developers, Vext Labs and xet7 !
Cancel workflow does not cancel a job whose if: is still true, and always()
is true in a cancelled run. tests/releaseCancelSafeJobs.test.cjs checked
only release-all.yml; it now fails on a job on always() in any workflow that
is a reusable-workflow call or has no timeout of 60 minutes or less. Every
such job today (release-notes, done, and the AppImage, Flatpak and Mac
summaries) is bounded. node-patches had eleven five-hour resume stages
called this way, and Cancel workflow did not stop them.
Thanks to xet7 !
The Recover job of "Release All missing" died after one HTTP 502 from the
GitHub API while `gh run watch --exit-status` listed the rerun's jobs, so
plan and every build after it were skipped. releases/retry-failed-release.py
now polls `gh run view`, retries up to ten API errors in a row, waits for
the new attempt rather than reading the old attempt's failure, and stops
only on the rerun's own conclusion. tests/retryFailedRelease.test.py pins
the 502 case, a failed rerun, a lasting outage and the stale attempt, and
that no `gh run watch` is left.
Thanks to xet7 !
The board publication finds a board's comments, comment reactions, text
notes, checklists, checklist items and attachments by the board id each
row carries (#6480), but a card that changed board re-homed only its
checklists and checklist items. Its comments stayed published to the
board it left, and came back when the card was moved back. Attachments
followed only when Card.move() ran on the server, and the REST card
move, a hook-less write, re-homed nothing at all.
One list (models/lib/cardChildrenBoard.js) now names every such
collection, and one helper (server/lib/cardChildrenBoardSync.js)
re-homes them from the Cards.after.update hook, the REST move and the
durable rule move. The startup schema upgrade step card-children-boardid
moves rows that a move already left behind to their card's board.
Thanks to fabiosalles and xet7 !
Fixes#6793.
From IRC: a Jira import of 25 issues worked and one of 858 failed. The
import boundary refused any document over 250,000 values, and a Jira
search export carries some 250 per issue even without comments - mostly
null custom fields, avatar URLs and status categories - so it stopped at
under 1,000 issues, about 6 MB. The refusal was a plain Error, which the
import page showed as "Internal server error". The cap is now four
million values; a file over it is refused as import-too-large, which the
page translates, with the limit as the reason. The import deadline was a
flat two minutes whatever the file held; it is now WEKAN_IMPORT_TIMEOUT_MS
plus half a second per record (models/lib/importDeadline.js), on the
server and in the import page's watchdog alike. The Jira creator also
collected the file's issue keys once per issue, which was quadratic.
"Is there a way to convert an existing board into a template?" There was
not: a board template could only be built inside the Templates board.
Board menu / Save as Template (saveBoardAsTemplate) copies the board as a
template board, private with the creator as its one member, and adds the
linked card in the user's Templates / Board Templates swimlane that makes
it a template. Board admins only, as Copy Board, and not offered on a
board that already is a template.
The new import-too-large string is translated into every language; the
low-confidence ones are ace, ak, an, ay, bm, bo, bua, chr, csb, cv, dz, ee,
ff, fj, fo, fur, gn, gv, haw, hsb, iu, kl, kok, ks, kw, lg, lld, nah, nap,
nd, rm, rup, sah, sc, scn, se, ss, tig, tlh, to, ve, ve-PP, ve-CC, vo, wa,
wa-RR, wal, wo, wuu-Hans and zgh.
Thanks to xet7 !
Fixes#6792.
CodeQL alert 590 (js/incomplete-sanitization): the #6788 e2e put the
stored address into a $regex with only "." escaped, so a backslash in the
address could cancel the escape. It now uses escape-string-regexp, the
same module the server matches addresses with.
tests/incompleteEscaping.test.cjs only knew single-character patterns
and so missed `.replace(/[.]/g, '\\.')`. It now also reports a character
class without a backslash replaced by a backslash escape, and found this
line before it was fixed.
Thanks to GitHub CodeQL and xet7 !
tests/adminPanelDocsMatchMenu.test.cjs requires docs/Features/Admin-Panel/Validation.md
to inventory every live pane; Attachments / Retention (#6789) was missing
from it. It also crashed on the commit before this one, whose test run was
read by its last line only.
Thanks to xet7 !
The full node suite found three breakages in this round's own work:
- adminPanelDocsMatchMenu: Admin Panel / Attachments / Retention was in the
product and not in docs/Features/Admin-Panel/Attachments/README.md. It has
its section and its row in the URL table now.
- incompleteEscaping: the test from the #6788 patch hand-wrote a regex escape.
It now uses the same escape-string-regexp package the server imports.
- releaseSnapArches: two guards read the Launchpad step's text. infra_failure()
(the v12.56 Launchpad-outage retries) also reads snapcraft-wekan-*.txt
before the loop, and the fixed `sleep 60` became `sleep "$pause"`. The
guards now look for the log printing after the no-snap message, and for the
pause variable, and say why. These two had been failing since that change.
An earlier run's report that this suite was only "load-related" was wrong:
the FAIL lines had been filtered out of the output that was read.
Thanks to xet7 !
An audit of every environment variable the code reads found 30 that are new
since v12.00. Eleven were already Admin Panel settings and on every platform
the release workflows build. Of the rest:
Now Admin Panel settings, through models/lib/authConfigCatalog.js (a value
there wins, an empty field falls back to the variable):
- Email to card limits, beside its own Admin Panel settings:
INBOUND_IMAP_POLL_SECONDS, INBOUND_EMAIL_PER_ADDRESS_PER_HOUR,
INBOUND_EMAIL_MAX_BYTES, INBOUND_EMAIL_MAX_ATTACHMENT_BYTES and
EMAIL_VIEW_SENDS_PER_HOUR. They were module-level constants, read before
the Admin Panel values loaded; they are read when used now.
createRateLimiter takes a limit function, and a changed poll interval
restarts the IMAP poller.
- ACCOUNTS_COOKIE_REFRESH_RATE_LIMIT in People / Login, like the login
expiry beside it, applied from the next start.
- Windows SSO (WINDOWS_SSO_ENABLED, _DOMAINS, _ACTIVE_DIRECTORY,
_MERGE_EXISTING_USERS) in People / Login. It was already read through
authEnv but had no form. Being Windows-only, its catalog section names
start-wekan.bat as its one platform.
Listed on every platform (Dockerfile, the docker-compose files, start-wekan.sh
and .bat, the snap config and help), where none of these had been: the
variables above, INBOUND_EMAIL_WEBHOOK_SECRET (its own Admin Panel page
already has it), and the per-process switches that stay environment only -
INBOUND_IMAP_DISABLED (one pod of a multi-pod deployment), WEKAN_DISK_MONITOR,
WEKAN_DISK_WATCH_PATHS, WEKAN_RAM_MONITOR and WEKAN_IMPORT_RUN_STALE_MS.
Left unlisted on purpose: NEXTCLOUD_EXAPP, NEXTCLOUD_URL and
WEKAN_NEXTCLOUD_PROXY_KEY (set by Nextcloud's AppAPI), SANDSTORM_MAILDIR (set
by Sandstorm). DEFAULT_AVATAR_URL keeps no Admin Panel UI, as decided in #824.
tests/envVarCoverage.test.cjs keeps it so. It fails for a variable that is in
no Admin Panel catalog, OWN_PANE or ENV_ONLY with its reason; the variables
read before v12.00 are a frozen LEGACY list that can only shrink. An
environment-only or own-page setting a user sets must be on every platform,
and stale entries fail. tests/authConfigCatalog.test.cjs honours a section's
platform list. tests/adminPanelNewSettings.test.cjs pins the reads and the
forms. tests/cookieRefreshRateLimit.test.cjs's guard now expects the Admin
Panel value, and says why. tests/playwright/specs/admin-new-env-settings.e2e.js
opens the forms.
Thanks to xet7 !
The result of an Admin Panel backup lived only in the memory of the server
process that ran it. After a restart, or on another pod of a multi-pod
deployment, the Backup pane said "idle", so nobody could see whether last
night's backup had worked. There was no history, no warning when a scheduled
backup failed or did not run, and local backups were never cleaned up.
- Every run, scheduled or "Backup now", is recorded in backupRuns: start and
end, who or the schedule, scope, storage, status, size, duration and error.
Records are kept 400 days. A run still "running" after 24 hours counts as
failed, and a scheduled run skipped for another backup is recorded as failed.
- Admin Panel / Problems / Backup shows the last success (overdue when its
period has passed), the last failure with its error, the next run and the
last 30 runs. The same status is above the schedule in Attachments / Backup.
- Problems / Summary lists "Backup failed" and "Backup overdue". /metrics adds
wekan_backup_last_success_timestamp_seconds,
wekan_backup_last_run_timestamp_seconds, wekan_backup_last_run_success and
wekan_backup_overdue for alerting.
- BACKUP_KEEP_DAYS: after a successful backup, local backups of the same scope
older than N days are deleted. The newest is always kept, and only a
directory inside the backup root holding a backup.zip is removed.
From the patch by Raymond-Voogt, and in Admin Panel as well:
- BACKUP_KEEP_DAYS is the catalog's backup section
(models/lib/authConfigCatalog.js), overridable beside the schedule in
Admin Panel / Attachments / Backup and resolved through authEnv at every
backup.
- It is listed on every platform: the compose files, start-wekan.sh and .bat,
the snap config and help, and the Dockerfile, which the patch's version
had missed. Its platform hunks, written before #6789, were re-applied by
hand.
tests/backupRuns.test.cjs (21): the run records, overdue and pruning against a
real directory, and the Admin Panel override, including that nothing reads the
variable around it. tests/authConfigCatalog.test.cjs checks every platform.
admin-attachment-retention.e2e.js checks the Backup pane shows the field.
Thanks to Raymond-Voogt and xet7 !
Fixes#6790.
Organisations that must not keep files forever (storage limits, GDPR data
minimisation) had only scripts working on MongoDB directly, which leave card
references behind, do not know which storage holds a file, and break on
upgrades. Soft-deleted attachments were also kept forever.
A nightly job (server/attachmentRetention.js, rules in
models/lib/attachmentRetention.js), off unless configured:
- ATTACHMENTS_RETENTION_DAYS soft-deletes card attachments older than N days
through the card's own Delete (cover unset, activity, card history Restore).
- ATTACHMENTS_RETENTION_MODE: uploaded (default), or card-inactive, which also
requires the card to have had no activity for as long.
- ATTACHMENTS_PURGE_DELETED_AFTER_DAYS removes attachments soft-deleted at
least M days ago for good, file included, through Attachments.removeAsync
so every storage backend is cleaned. The write re-checks deletedAt, so one
restored meanwhile stays.
- ATTACHMENTS_RETENTION_DRY_RUN only logs; ATTACHMENTS_RETENTION_MAX_PER_RUN
caps each step per night.
Never touched: attachments without a card, template boards and cards, and
attachments with an unknown upload date.
From the patch by Raymond-Voogt, and in Admin Panel as well:
- The five settings are a section of models/lib/authConfigCatalog.js.
Admin Panel / Attachments / Retention (/admin/attachments/retention) draws
the shared override form on it: a value there wins, an empty field falls
back to the variable, and the source in effect is shown.
- The job resolves them through authEnv on every run, so a change needs no
restart.
- The new menu label attachment-retention is translated in all 246 locales.
- The catalog's platform test then found that the patch had missed the
Dockerfile and the snap help; both list all five now, beside
docker-compose, start-wekan and the snap config.
tests/attachmentRetention.test.cjs (14) covers the rules and the Admin Panel
wiring; negative: the job never reads the environment around the Admin Panel.
tests/authConfigCatalog.test.cjs checks every platform lists each variable.
tests/playwright/specs/admin-attachment-retention.e2e.js opens the pane, and
checks that an ordinary user cannot.
Thanks to Raymond-Voogt and xet7 !
Fixes#6789.
A board member who was not a board admin could not add a colleague from
Board / Members / +.
- inviteUserToBoard matched the typed email address exactly. LDAP and OIDC
store an address as the directory spells it ("Jan.Jansen@Example.com"), so
"jan.jansen@example.com" found nobody, and WeKan tried to create a new
account instead: "This user is not created" with registration off,
otherwise a raw 403 "Email already exists". The match is now
case-insensitive, anchored and escaped.
- searchUsers read five users and filtered afterwards: disabled accounts
(which the invite then refuses, #1894) and, for non-admins, people outside
the user's organization or team (#6116). Five non-matching users ahead of
the colleague hid them. It reads 50, filters, then returns 5.
From the patch by Raymond-Voogt. tests/inviteMemberEmailCase.test.cjs pins
both, including that the pattern is anchored and its dot literal.
tests/playwright/specs/invite-member-email-case.e2e.js covers:
- an invite with a lower-cased address adds the stored colleague, and no
second account is made;
- negative: a similar but different address is not taken for them;
- the search returns the colleague past six disabled matches, offering none
of them.
Thanks to Raymond-Voogt and xet7 !
Fixes#6788.
The Security report in Finnish read "Sijainti" beside the English "IPv6
location", and the menu row and title bar of the Instrumentation report were
English in every language.
- The IPv6 location column is the translated 'location' with the fixed
technical suffix " (IPv6)" - "Sijainti (IPv6)" - through a new optional
labelSuffix on table headers, so no new key is needed.
- instrumentationReportTitle names the report in the menu row and in its
title bar, which tests/adminUrls.test.cjs requires to say the same words.
It is translated into all 246 locale files, after instrumentation-other
in en.i18n.json's key order. French uses "Instrumentation logicielle",
because the bare word is spelled exactly like the English one.
Low-confidence translations, written from the language's general
vocabulary for "measuring tools" where the technical term has no
established form: ak, ay, bm, bua, chr, cv, ee, ff, fj, gn, gv, ha, haw, ig,
iu, kl, kw, lg, mi, nah, nd, ny, om, qu, rn, rw, sah, se, sm, sn, ss, st,
tig, tlh, to, tpi, ts, ve, vo, wal, wo, zgh.
tests/addressReportColumns.test.cjs checks that every address column header
is translated. The admin-office-location UI spec expects "Location (IPv6)".
Thanks to xet7 !
Admin Panel / Problems / Security showed a sign-in from "Dinkelsbühl, DE".
Cloudflare sends cf-ipcity as raw UTF-8 bytes, and Node hands HTTP header
values over as Latin-1, one character per byte, so every city with a letter
outside ASCII was stored garbled.
geoHeaders reads a header value made only of byte-sized characters as UTF-8
when its bytes are valid UTF-8, before the existing percent-decoding. Real
Latin-1 that is not valid UTF-8, plain ASCII and percent-encoded names are
left as they were. Rows already stored keep the old text until their place is
seen again.
tests/geoHeadersUtf8.test.cjs: Dinkelsbühl, Zürich, Kraków, Москва, 東京, and
an end-to-end HTTP request carrying the UTF-8 bytes. Negatives: ASCII, a
percent-encoded city and genuine Latin-1 are unchanged. It fails without the
fix.
Thanks to xet7 !
Admin Panel / Problems / Security showed StorageBleed, "checked 12144 files in
900s; 0 changed, 0 with no record saying why; stopped early: this run has
used its time", 41 times: the background scan reported its own summary on
every run. A clean scan is the scan working, and the Security report is for
attempts. A summary that has nothing to sum up hides the row that matters.
The summary is recorded only when the scan found a changed or missing file,
beside those findings, which are still recorded one by one. A clean run,
including one that ran out of time and continues next time, is written to
the server log as "[file-integrity] ...".
tests/fileIntegrity.test.cjs pins the condition. Negatives: no unconditional
summary record remains, and changed and missing files are still recorded.
Thanks to xet7 !
Admin Panel / Problems / Security showed FileNameBleed, "uploaded a filename
carrying an exploit pattern; URL-encoded name", for a user who had only
opened a board of 2019 photos with Cyrillic names. Reading each file
repaired its stored percent-encoded name (the FileBleed "fileRead" rows,
correct and recorded once per file). The repair's reasons were then passed to
the upload canary, where "URL-encoded name" counted as dangerous. Browsers and
older WeKan percent-encode every non-ASCII name, so every such name tripped it.
- "URL-encoded name" is no longer a dangerous reason.
- What an encoding could hide is named from the decoded name instead:
sanitizationReasons() now reports "path traversal" and "null byte". Before,
"URL-encoded name" was the only thing an encoded "../../etc/passwd" tripped;
it now trips the path-traversal canary under its own name, which
DANGEROUS_REASON already listed but no reason ever produced.
- A name repaired while a file is read (source fileRead) never trips the upload
canary: the repair is still recorded, but it is not an attempt.
tests/filenameCanaryReasons.test.cjs loads the real module, with its server
dependencies stubbed:
- the Cyrillic name trips nothing;
- negatives: an encoded traversal and an encoded null byte still trip, and
markup in a name uploaded now still trips;
- a read-time repair is recorded without a canary.
Thanks to xet7 !
Admin Panel / Problems / Security reported HistoryIntegrity checksum-mismatch,
severity critical, 89472 times, on a board's history row nobody had touched.
Meteor connects to MongoDB/FerretDB with ignoreUndefined, so a content
property that is undefined is dropped when the row is stored. Writers record
such content routinely, for example `deletedAt: list.deletedAt` for a list
that was never deleted. The row's hash covered `"deletedAt":{"$undefined":true}`,
the stored row has no deletedAt, and every background audit found the row
failing its own checksum and reported it again.
- hashHistoryRow() hashes previousContent and newContent as stored: undefined
properties dropped, undefined array elements as null, as the driver does.
New rows verify as the database returns them.
- Rows written before this were hashed with the undefined keys. If the normal
check fails, the keys present in the other side of the change and absent
here are re-added as undefined, which reproduces exactly what was hashed;
nothing else is accepted.
tests/changeHistoryIntegrity.test.cjs stores rows through BSON with
ignoreUndefined, exactly as the driver does: a new row and a pre-fix row both
verify, and a changed value, a removed real value, a change on the other side
and an added key are still caught. The test fails without the fix.
A row that still reports checksum-mismatch after this release changed in a
way an unset value cannot explain, and is worth looking at.
Thanks to xet7 !
tests/releaseDownloads.test.cjs flags every workflow line that names curl
as a download that must go through releases/fetch.sh; infra_failure()'s grep
pattern named it only as text in git's error. 'RPC failed;' identifies that
error on its own. tests/snapLaunchpadInfraRetry.test.cjs still runs the
function against v12.56's 'RPC failed; curl 56' log.
Thanks to xet7 !
v12.56's ppc64el snap failed after 20 minutes of its five-hour budget. Attempt
1's Launchpad builder lost its TLS connection while cloning our source ("RPC
failed; curl 56 GnuTLS recv error ... fatal: early EOF"). Attempts 2 and 3
followed a minute apart each, and both met Launchpad's own "HTTP Error 503:
Service Unavailable" / "Error: Timeout". s390x, riscv64 and armhf each lost an
attempt the same night and recovered.
infra_failure() recognises an attempt that failed on Launchpad rather than on
WeKan: an HTTP 5xx or timeout from its API, or, in that attempt's own build
log, a dropped git/TLS transfer or the build farm's package proxy failing.
Such an attempt earns another one, up to six, after a pause of 5, 10, 15...
minutes, never running past the shared deadline. Every other failure keeps
three attempts and the 60s pause; a missing stage-package is not retried
longer.
tests/snapLaunchpadInfraRetry.test.cjs runs the workflow's own infra_failure()
against logs shaped like v12.56's, and against the negatives: a missing
stage-package, an earlier attempt's log, a 4xx.
Thanks to xet7 !
v12.56's Bun linux-x64 bundle failed its live smoke test: WeKan stopped with
"Topology is closed", because nothing listened on 127.0.0.1:27019. The
smoke-test MongoDB had been started with `docker run -d --rm` and never
checked, so a container that had died left no log behind, and the job could
not say why. linux-arm64 passed in the same run, and linux-x64 in v12.53, so
this happens only sometimes.
- bun.yml starts the container without --rm, and waits until it answers
a ping. If it does not answer within 60s, its state and log are printed.
- build-target.sh checks the port again right before it starts WeKan, with
the new releases/bun/wait-mongo.mjs (a plain TCP check, nothing to install).
The database holds only this test's data, so if it is gone, its log is
printed and a new one is started once. A second failure fails the job.
tests/bunSmokeMongo.test.cjs: URL parsing, the wait succeeding and timing
out, the workflow step and the single replacement.
buildScriptParity lists wait-mongo.mjs with the other Bun target-build
internals.
Thanks to xet7 !
The UI test of #6783 (inactive-users-last-active.e2e.js) still found "-" in
another user's row after the people publication started sending
lastConnectionDate. The table builds its rows from a client-side Users.find
with its own field list (peopleDocs), which left the date out. The row's
lastActiveAt read that copy before the full user document, so the date never
reached the cell. The projection keeps the field now. The two presence
helpers read the whole cached user, as userData() already does, so a field
missing from the projection cannot hide them again.
Verified with Playwright on Chromium against the dev server: the spec failed
before this change and passes with it. tests/lastActive.test.cjs pins both
halves and fails without this commit.
Thanks to Raymond-Voogt and xet7 !
Fixes#6783.
A check mark is not mirrored in a right-to-left language (#6786), so the
tick's rule uses border-right. The guard allows border-left/-right only inside
a rule whose selector is a checked state, the same definition
tests/rtlCheckMark.test.cjs uses. A border anywhere else, and any margin,
padding or float inside such a rule, is still refused.
Thanks to mimZD and xet7 !
Its wekan service must match the other compose files line for line
(tests/dockerComposeBackends.test.cjs): the setting moves from beside
ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS to after LOGOUT_ON_MINUTES, with the
Admin Panel note the others carry.
Thanks to Raymond-Voogt and xet7 !
Admin Panel / Problems / "Lists missing their swimlane" -> Restore binds each
list back to the swimlane positionHistory records it was created in. After
every new WeKan release, the startup schema upgrade re-checks old data, and
its merge-per-swimlane-lists step - written for the v7.98 era, which stamped
lists to the default swimlane and duplicated them per swimlane - took those
lists for that era's damage. It ran when a board had two lists of one title
or one list with cards in another swimlane, and then un-bound every list on
the board, the restored ones included. So after each upgrade the count came
back to exactly the same number.
A list bound to the very swimlane it was created in, with none of its cards
in another swimlane, is per-swimlane by design: it is what Restore puts back,
and not a v7.98 stamp, which was applied after the list existed. The step now
leaves those lists out of its check, its merge and its un-binding, so a board
of only such lists does not trigger it. Stamped lists, lists whose cards are
in other swimlanes, and lists bound anywhere but their record are handled as
before. Lists already un-bound by an earlier upgrade are restored by pressing
Restore once more, and now stay.
tests/schemaUpgradeSteps.test.cjs: the issue's board (restored lists kept, a
stamp on the same board still un-bound), a by-design board not triggering the
step, and the two negatives. The first fails without the fix.
Thanks to mimZD and xet7 !
Fixes#6787.
The tick is a box rotated 40 degrees, drawn by its bottom and side borders,
and the side was border-inline-end. A right-to-left page puts that on the
left, so the rotated corner opened the other way and every checked checkbox
showed "<" - the Admin Panel's bounced-email table in Persian, in the issue.
A check mark is not mirrored in a right-to-left language, so all 25 rules
that draw one - the shared materialCheckBox, checklists, subtasks, the
sidebar, the Admin Panel and every board theme's colour - use border-right
and border-left now. Where the box sits stays logical.
tests/rtlCheckMark.test.cjs fails if any checked state anywhere in client/
draws its tick with a logical border. tests/playwright/specs/rtl-check-mark.e2e.js
compares the computed borders in ltr and rtl. customFieldsSectionMenu's guard
now expects border-right, and its comment says why.
Thanks to mimZD and xet7 !
Fixes#6786.
Admin Panel / People showed "Last active" as "-" for every user but the
administrator: the people publication did not carry lastConnectionDate.
USERS_DISABLE_INACTIVE_AFTER_DAYS disables, once a night, accounts that have
not used WeKan for that many days, and revokes their logins. Admins are
never disabled. An account with no recorded activity starts its count on the
first run rather than from createdAt, and re-enabling an account restarts it.
The setting is in Admin Panel / People / Login as well as in the
environment, and the job resolves it every night, so a change there needs no
restart. Every platform's settings file lists it.
LDAP background sync looked a user up by id without LDAP_USER_SEARCH_FILTER,
so an account the filter excludes - one disabled in Active Directory - could
not log in but was still "found". It was never disabled, and a manual
disable was undone on the next run. The filter is now ANDed into the lookup.
An account disabled for inactivity is not re-enabled by the sync.
From the patch by Raymond-Voogt, with the setting moved into the Admin
Panel's login settings. Tests: tests/inactiveUsers.test.cjs,
tests/ldapBackgroundSync.test.cjs, tests/ldapUserIdFilter.test.cjs,
tests/authConfigCatalog.test.cjs and
tests/playwright/specs/inactive-users-last-active.e2e.js.
Thanks to Raymond-Voogt and xet7 !
Fixes#6783,
Fixes#6784,
Fixes#6785.
release-all's glibc "AppImages" call passed no `only`, so it built the musl
rows too, and both groups had an "AppImage x86_64" and an "AppImage aarch64".
Two jobs of one name write to one directory of the run's log archive, whose
files then ask to be overwritten or renamed when it is unpacked.
- The glibc call builds x86_64, aarch64, i686 and armhf only. The musl pair
stays with the "musl AppImages" call, which waits for the musl bundles.
- The job is named by its file's architecture, so it is "AppImage
x86_64-musl", which the publish step's "see the AppImage <arch> build job's
log" names too.
- The publish step checks only the architectures its own run builds. The musl
run warned that the four glibc AppImages were missing.
tests/muslBundles.test.cjs checks that no two matrix rows share a name, and
that each row is built by exactly one call.
Thanks to xet7 !
v12.54's armv7 single file failed with std::bad_alloc on all three attempts
of the retry added for armhf in v12.53, so the failure is not intermittent.
--build-sea injects with LIEF inside the Node.js that runs it, and a 32-bit
target's blob must be written by a 32-bit Node.js. The executable, the
180 MiB archive and LIEF's copies of both do not fit that process's address
space.
For a 32-bit ELF target the i386 Node.js now writes only the blob
(--experimental-sea-config: the same blob, no LIEF), and the new
releases/single-file/sea-inject-elf.py injects it with LIEF 1.0.0 from PyPI,
the version Node.js vendors. It ports InjectIntoELF() and MarkSentinel()
from src/node_sea_bin.cc step for step. Its output is byte-identical to
--build-sea's, for a non-PIE x64 Node.js and for the PIE node-armv7. win32
(PE) and 64-bit targets keep --build-sea. The retry is removed.
tests/singleFileSeaInject.test.cjs checks the split and the workflow's LIEF.
Where a Python with LIEF 1.0.0 is available it builds both ways, compares
them byte for byte, runs the result, and checks that an executable that
already carries a blob is refused.
Thanks to xet7 !
Every x86_64 musl AppImage of v12.53 failed to start in a bare Alpine:
"Error loading shared library libatomic.so.1". node-runtime held only
libstdc++ and libgcc_s; the build container had libatomic through g++, so the
bundle's own smoke test passed.
build-bundle.sh now copies every NEEDED library of bundle/node except musl's
libc, and AppImage.yml checks each is in node-runtime before packing.
Thanks to xet7 !
The v12.53 armhf single file failed with std::bad_alloc in --build-sea. A
32-bit target is injected by the 32-bit i386 Node.js, and the executable,
the 180 MiB archive and LIEF's copies of both come near its 4 GiB address
space. armv7 and armv6, same archive and host, built in the same run, and
armhf built in v12.52, so the failure is intermittent.
buildSea() runs it up to three times when, and only when, it fails with
std::bad_alloc, removing the partial output between attempts. Any other
failure stops at once.
Thanks to xet7 !
Every Android bundle of v12.53 stopped in npm-rebuild.js: "sh: 1:
node-gyp-build: not found". Termux has no /usr/bin/env, so a
"#!/usr/bin/env node" script runs only through termux-exec, an LD_PRELOAD
library that Termux's login exports. The workflow starts bash directly,
without login.
build-bundle.sh exports termux-exec's library the way login does, newest
generation first, and keeps it only if a program still starts with it.
Thanks to xet7 !
The activity hook delivered webhooks through Meteor.call('outgoingWebhooks'),
and that method trusted a call without this.connection as the server's own.
Meteor gives a server-side Meteor.call the userId AND connection of the method
it runs in (ddp-server applyAsync), and a card moved from a browser runs in
that browser's method. So the server's own act-moveCard delivery arrived as a
client forging one: it was recorded as ssrf.webhook-forge (high) with the
mover's userId, and blockOnSecurityEvent set loginDisabled.
- deliverActivityWebhook() in server/notifications/outgoing.js is the server's
delivery: a function, not a method, so no client reaches it and no inherited
context changes it. It reads the integration again (enabled) and names the
activity's actor. server/models/activities.js calls it through safeDeliver.
- The outgoingWebhooks method treats every call as a client's: only the
card-opened notification, rebuilt from the card, as before for clients.
- Server deliveries no longer need the actor to be a member of the hook's
board, which also skipped a global webhook ('_global' is no board) and an
activity with no method behind it (REST, timers).
- securityLog.record() now passes the catalog key and blocksAccount to the
block decision. The stored row carries neither, so every 'high' blocked row
disabled whoever it named: a hook refused for its own internal address
disabled the mover too, and ssrf.redirect/ssrf.attachment's
blocksAccount: false never applied. ssrf.webhook is now blocksAccount:
false - the person whose move sent it did not write the hook's URL.
tests/hookBleedServerDelivery.test.cjs: the move delivered with nothing
logged, in an inherited context too; a forged call still refused, logged and
blocking; the card-opened notification; failing and internal hooks blocking
nobody, through record() itself; and a whole-tree scan that no server code
calls a this.connection-gated method from inside a client's method without
clearing the invocation. tests/playwright/specs/hook-bleed.e2e.js moves a
card through the Move dialog with a webhook enabled.
Thanks to do4me and xet7 !
Fixes#6782.
bsdBundles pinned bsd.yml's FreeBSD VM to the old commit; it now reads the
pin and requires node-patches to use the same one, which it now does.
sqrl required qrcode-generator 1.4.4 exactly; it now requires an exact pin,
and 2.0.4 was checked to give sqrlQrSvg's call the same SVG.
Thanks to dependabot and xet7 !
v12.51's i386 single file stopped with std::bad_alloc and freebsd-x64's with
"Failed to relocate ELF program headers": the i386 Node.js is a PIE with
R_386_32 relocations LIEF does not adjust, and the FreeBSD one a non-PIE with
two bss-like segments. releases/single-file/sea-inject.mjs reads that from
the ELF before anything is built, build.mjs exits 3 with the reason, and
single-file.yml turns it into a notice. wekan/node-patches now builds both
the way the injection needs.
A target that was not built leaves a one-line artifact saying why, so its
Start job no longer logs "Artifact not found" errors and publish does not
warn about it as unstarted.
Thanks to xet7 !
Every musl AppImage of v12.51 died at once in Alpine: "Error relocating
.../bundle/node: _ZTVN10__cxxabiv117__class_type_infoE: symbol not found".
The musl Node.js is linked against GCC's C++ runtime, which musl.yml's own
Alpine had installed for building, and a user's Alpine does not. The bundle's
node-runtime now holds that Alpine's libstdc++.so.6 and libgcc_s.so.1 with a
license notice, start-wekan.sh puts it on the library path as for libatomic,
and the AppImage workflow requires both files of a musl bundle.
Thanks to xet7 !
Every Android bundle of v12.51 stopped with EPERM: npm chmods each package's
bin when it installs (uuid/dist/bin/uuid), and only a file's owner may chmod
it - a+rwX on the bind mount was not enough. android.yml reads the uid of
Termux's user from the image, chowns the checkout to it before the rebuild,
and takes it back before packaging. An unreadable uid stops the job.
Thanks to xet7 !
js/incomplete-multi-character-sanitization in
tests/templatesAreRendered.test.cjs: the test removed template declarations
and Template.<name>.helpers/events/onCreated/onRendered/onDestroyed from the
source it scans with a single replace each, so removing one could splice its
neighbours into another (and CodeQL read the remains as a possible on...
attribute). Both removals now repeat until nothing changes, the fix used for
alerts #545 and #570-#582.
tests/tagStrippingFixedPoint.test.cjs keeps the shape out of the whole tree:
it now also finds a multi-character removal of an on<Capital> name outside a
fixed-point loop, and proves it catches alert #588's code and passes the
loop. The code is a test's source scan, never run on user input, so nothing
reaches Admin Panel -> Problems.
Thanks to GitHub CodeQL and xet7 !
The theme follows Apple's Liquid Glass: the two header bars, swimlane
headers, lists, minicards, the opened card, the sidebar and popups are
frosted glass over the pastel mesh - a translucent fill,
blur(24px) saturate(180%), a rim of light along the edges (inset highlights,
brightest on top), a diagonal sheen and larger rounded corners. The header
bars are glass now instead of solid blue, with dark text; blue is kept for
the primary action, glossy, and for what is active. Reduce transparency, or
a browser without backdrop-filter, gets near-opaque surfaces.
A new UI test, theme-appleglasspastel-contrast.e2e.js, measures every text
and icon against what is really painted behind it - it hides the text,
screenshots, and reads the pixels under each box - to WCAG AA (4.5:1 text,
3:1 large text and icons), on the board, the opened card, the sidebar, the
board menu and All Boards, at laptop and phone size. It found and the theme
now fixes: white header buttons, icons, username and the stock logo on the
glass; the sidebar hamburger forced white by boardBody.js's accessibility
style; grey #808080 sidebar headings (3.8:1); #a6a6a6 icons (2.4:1); faint
Add Card; white text on the pale-blue hovered menu row; and the list
headers list.css pins grey with !important.
The header guards headerBars and themeAccents now compare a bar's whole
background, so a glass gradient counts, and list appleglasspastel as a glass
header whose accent is its primary blue.
Thanks to xet7 !
Card menu -> Drawing -> Open as whiteboard opens the drawing under the header
bar, over the opened card, with the drawing's and Red Strings' own tools -
the seven colours and four widths, Undo, Clear and Save picture. The 800 x
500 drawing is scaled to the screen, so a line lands in the same place on a
phone and on a 55" screen; Escape or x closes it.
Several people draw at the same time: each line is sent when the pen lifts
as one atomic $push into that drawing (cards.whiteboardAddStroke), with its
own id and its author, and the card's publication brings it to everyone who
has the drawing open; the line stays drawn locally until it comes back.
Undo and Clear take away only the caller's own lines, Save picture writes
only the drawing's PNG, so nothing anybody else is drawing is overwritten.
Who may draw is who may edit the card; the others watch. A Board Settings /
Card row, Whiteboard, hides it on a board, and the Admin Panel's Card fields
everywhere. The drawing popup's PNG upload is shared with Save picture.
Translated in every language.
Thanks to xet7 !
The last three of the features other kanban apps call AI, done with plain
code (docs/Features/General/Assistants/Assistants.md):
- Checklists -> From description: the list lines of a card's description
("- ", "* ", "1. ", task boxes; never inside code blocks) previewed, then
made into a checklist - ticked where the line was [x] - or into subtasks
through addSubtaskCard.
- Create cards from action items, on a comment and in the card menu for its
description: "TODO: ...", "- [ ] ..." and "@user ..." lines become cards
at the top of the card's list, linking back to it, with the mentioned
user as member when they are an active board member.
- Global search understands plain phrases in the reader's language - "my
overdue cards", "due this week", "unassigned cards" and seven more - and
runs them as its operators, saying which; a query with an operator in it
is never rewritten.
Checklist from description and Action items are Show on Card rows of Board
Settings / Card and of the Admin Panel's Card fields; Search phrases is an
Admin Panel / Settings / Visibility / Features tick. Translated in every
language.
Thanks to xet7 !
Five more of the features other kanban apps call AI, done with plain code
(docs/Features/General/Assistants/Assistants.md):
- Board menu -> Summary: what each person did in the last day or week -
done, moved, added, commented - and what they have overdue, from the
activities and History; Copy as text gives Markdown with card links.
- Board menu -> Since your last visit (N): what others changed since the
visit before this one, by card. The visit is kept in profile.boardVisits,
at most 500 boards.
- At risk: a weighted score of overdue, due soon with little done, blocked,
idle in a middle list and sent back; a triangle on the minicard and the
reasons in the opened card.
- Suggested members in the Members and Assignees popups, from who handles
the same labels, list and swimlane.
- Forecast done date: the 50th and 85th percentile of the board's cycle
times, counting only the cards that took at least as long as this one
has been open. The server sends the times once per board, numbers only.
Summary and Since your last visit are features of Admin Panel / Settings /
Visibility / Features; At risk, Suggested members and Forecast are rows of
Board Settings / Card and of the Admin Panel's Card fields. Every server
answer applies the board's read rules and the assigned-only role.
Translated in every language.
The guards of the earlier helpers are brought up to date as well: the date
words of models/lib/titleDueDate.js are a reviewed RegExp site,
server/methods/ruleSuggestions.js is a board-admin-only path, the Rules
page's list branch carries the suggestions, and Tigre's new strings use its
own word for a board.
Thanks to xet7 !
Board menu -> Rules shows Suggested rules above the rule list: when the
same person does one thing to a card and, within two minutes, another, that
pair is counted once per card, and a pair seen on three cards in the last 30
days is offered as a rule (moved to a list -> label, member or archive;
added to a list -> label or member; label added -> member). A pair a rule
already does is not offered. Create goes through rules.createRule with the
same trigger and action the wizard builds; Dismiss hides one suggestion for
that admin on that board (profile.dismissedRuleSuggestions).
Only board admins get suggestions, and Admin Panel / Settings / Visibility /
Features has a Rule suggestions tick that turns them off, on the server too.
Translated in every language.
Thanks to xet7 !
Two of the features other kanban apps call AI, done with plain code
(docs/Features/General/Assistants/Assistants.md):
- A due date typed at the end of a new card's title ("... tomorrow 14:00",
"huomenna klo 14", "friday 9:30", "in 3 days", "23.10.") becomes the card's
due date, read with the browser's own Intl tables so every language works.
A hint under the field shows the date, and its x keeps the words. The
opened card's title reads it too, but only when the title changed.
- Similar cards: letter triples and the Dice coefficient (letter pairs for
scripts without spaces). The add-card form lists up to three close cards,
marking a possible duplicate, and the opened card has a Similar cards
section. The server method applies the board's read rules and the
assigned-only role.
Both are rows of Board Settings / Card and of Admin Panel / Settings /
Visibility / Features / Card fields, so a board admin or a site admin can
hide them. Translated in every language.
Thanks to xet7 !
The page listed up to 200 methods and publications on 11 pages. Above the
table there is now one bar per area - Settings, Login, People, Boards,
Cards, Lists, Notifications, Attachments, Problems and the rest - its length
the area's calls against the busiest area's, its red end the share that
failed, with the calls, the average duration and the errors beside it.
Clicking an area shows only its operations in the table; clicking it again
shows all.
models/lib/instrumentationAreas.js groups an operation by the first area
whose pattern matches its name, and weights an area's average duration by
completed calls. tests/instrumentationAreas.test.cjs checks the grouping
against all of WeKan's 614 methods and publications - "Other" holds three -
and the summary arithmetic. The labels are existing translation keys, apart
from the new instrumentation-other ("Other"), translated into every
language. The Finnish "cards" heading read "Korttia" (the form after a
number) in every place it is used as a heading or label; it is "Kortit".
The bars are HTML and CSS, one grid with subgrid rows so names, bars and
numbers line up; the viewport matrix checks the page at every device size.
Thanks to xet7 !
Admin Panel / Settings / Shared labels chose a colour from a dropdown of
colour names. The board label popup's picker - the palette swatches and the
custom colour wheel - is its own template now, labelColorPicker, and the
popup and every Shared labels row use it; a custom colour is saved as its
hex, which the server already accepted. A pick reaches the row through an
object with set(), because Spacebars calls a function argument instead of
passing it.
The swatches grow to fill their row, so on the wide admin page their width
followed the window. There they have the size they have in an opened card's
label popup, 54 x 33px, at every window width.
tests/sharedLabelsColorPicker.test.cjs pins the shared picker and the size;
tests/playwright/specs/shared-labels-color-picker.e2e.js picks a swatch and a
custom colour, saves both, and measures the swatches at 1366, 1920 and
2560px.
Thanks to xet7 !
The add-list form's alternatives are one span.quiet each in .edit-controls,
a flex row; a flex item drops the space its text begins with, so it read
"or Templateor link a list from another board". Adjacent .quiet spans get a
margin between them instead.
Every dev-server start also warned "missing space before text for line 26"
of editor.jade: inlineViewer had a bare {{> UI.contentBlock }} line under
+viewer. It is +Template.contentBlock, as elsewhere.
tests/jadeCompilesClean.test.cjs compiles every client template with the
build's own Jade compiler and fails on any warning or error.
Thanks to xet7 !
The foldable in the screen-size test was a Galaxy Z Fold 7 standing in for
the iPhone Duo. Apple's specs (apple.com/iphone-duo/specs) give its inner
7.6" display as 1878 x 2670 px at 430 ppi and its outer 5.4" display as
1398 x 2034 px at 460 ppi; at the 3x of every current iPhone that is
626 x 890 and 466 x 678 CSS pixels, and the screenshots come out at exactly
Apple's pixel sizes. Both displays are tested upright and on their side with
the iPhone user agent and touch; all 40 checks pass.
Thanks to xet7 !
tests/playwright/specs/viewport-matrix.e2e.js opens WeKan as an iPhone 15,
an unfolded Galaxy Z Fold 7, an iPad Pro 11, a 1366x768 laptop and a 55"
4K touch panel, each upright and on its side, and checks the board header,
board menu -> Original positions, Problems -> Memory Usage and Disk usage,
the Table, Calendar Mode, Time, Timeline and Map views, and dragscroll by
mouse or by finger. It measures that icons and texts are on screen and not
drawn in the colour behind them, that the page never scrolls sideways, that
every column can be reached and stays readable; screenshots of each step go
to .tools/log/viewports/. What it found, fixed:
- iPhone: the header bar was width: 100% plus its side padding as a content
box, 24px wider than the screen, and the sidebar hamburger sat past the
right edge. It measures its padding inside now.
- Admin Panel left menu on a phone: the list's disc bullets showed as a dot
after every entry, and a rule between groups shrank to a dash beside an
entry. No bullets, and each group starts a row of its own.
- Admin tables: 13 equal columns were 25px each on a phone and 42px on an
upright iPad, every header one letter per line. Up to 1024px a column keeps
a readable floor and the table scrolls sideways in its wrapper.
- Table view: the title-wrap and swimlane-group toggles were white icons on
white - two empty squares - stretched to 72px by the global button padding.
And on an upright iPad or foldable, Members, Labels and the date columns lay
past the edge of an overflow-hidden board with nothing to scroll; the view
scrolls sideways itself now.
A swipe that starts beside a list's resize handle resizes the list, as it
should, so the dragscroll check starts where a person pans. The file runs in
Chromium only: device emulation and the touch swipe exist only there.
Thanks to xet7 !
fixedVulnerabilityClasses reads entry.path as an archive entry and asks for
a containment check; the disk monitor's loop variable was only named that.
Thanks to xet7 !
Where each swimlane, list and card was put when it was made has been
recorded on creation for a long time, and shown by nothing: the two
templates for it were drawn nowhere, every string was English and they
printed raw ids, and the client helpers beside them were never called.
Board menu → Original positions (after Archive, open to everyone who sees
the board) opens originalPositionsSidebar: Swimlanes, Lists and Cards, each
by the name it has now, Moved when it is no longer where it was put, where
it was put by the names those have now, the title it was made with when that
changed, and when. The server already narrows the history to what the
reader may see, assigned-only roles included.
The text is existing translation keys plus one new one, original-positions,
translated into every language. models/lib/originalPositions.js builds the
groups and is tested without Meteor; the old templates, the per-item
component and the uncalled helpers are removed, and the design page is now
the feature's guide.
Thanks to xet7 !
The RAM and Disk usage pages were designs that no code carried out. Both
monitors now run beside the CPU one and, like it, record only the start and
the end of a sustained high-usage period:
- Memory: RAM from /proc/meminfo's MemAvailable - os.freemem() is MemFree,
which counts the page cache as used and shows a healthy server as full -
and swap, which alone also starts a period. Without /proc/meminfo it uses
what Node reports. Settings WEKAN_RAM_* and WEKAN_SWAP_HIGH_PERCENT.
- Disk: each filesystem WeKan writes to - the files root, its temp directory
for uploads and the snap's data directory - measured once per device, with
an episode of its own. WEKAN_DISK_WATCH_PATHS replaces the paths.
- Each report has a live line above its rows and a URL
(/admin/problems/ram, /admin/problems/disk). The menu and titles use the
existing memory-usage and sandstorm-disk-usage keys, which every language
translates, and the Summary names the cpu, ram and disk streams.
Observe and report only, as designed. The three monitors share one event
recorder (server/lib/resourceLog.js) and one intEnv (models/lib/intEnv.js),
which three files had each defined. The pure halves are tested without
Meteor, and resourceMonitorsWiring checks the wiring and that no copy of
either shared piece returns.
Thanks to xet7 !
Each was described only in Board Settings / Board View. Their guides are
written from the templates and the server code: what each shows, every
control, what can be changed and who may change it, where its settings
live, and the files and tests behind it. Board-Header/README.md links them,
boardViewSettings fails when a Board View entry has no guide of its own, and
TODO Later loses the gap and the code-to-combine item that is done.
Thanks to xet7 !
- Calendar, Calendar Mode and Multi Board Calendar showed archived cards
once the Archive sidebar had loaded them; every calendar selector leaves
them out now.
- Timeline offered "Restore to this state" to every viewer, and a refused
restore vanished, since nothing awaited it. The button is shown to those
who may change the card, and a refusal is reported.
- Table: a swimlane header row spanned a fixed 11 columns, one short with
all four date columns and too wide on a phone; it spans the columns the
table has. Its add-card error was an English sentence.
- Map showed the raw "upload-failed" for a failed upload; it is translated.
- client/lib/errorMessage.js turns a failed call into a message in the
reader's language: the translation when the error code is a translation
key, otherwise the translated "Something went wrong" with the server's
reason after it. 25 alert() calls with their own English fallback use it.
tests/errorMessageI18n.test.cjs runs the helper and fails on an alert()
that falls back to English again; calendarFilter tests the archived cards
for all four selectors.
Thanks to xet7 !
fileIntegrityScan.js asked cpuMonitor for currentCpuPercent, which it never
exported, so cpuPercent() was always NaN: the hourly scan never skipped a
busy hour and every finding recorded cpuPercent NaN. It reads
getCurrentCpu().percent now.
Five files imported names their modules do not export - UserAvatar,
AttachmentStorage and Settings as a named import - none used; they are
removed. tests/namedImportsExist.test.cjs checks every named import and
destructured require of an app module against what that module exports.
Thanks to xet7 !
Both views wrote out the same FullCalendar options - first day of the week
and the week numbers that follow it, time formats, the one-row toolbar,
translated button labels, height, locale and direction - about fifty lines
each. models/lib/calendarBaseOptions.js builds them once, given the
translation, the user's first day and the display options, and each view
adds only what differs: dragging and selecting, navLinks, business hours
and its events.
The tests that read the toolbar or the direction from boardBody.js read the
shared module, and the Multi Board Calendar runtime test runs it for real.
Thanks to xet7 !
Template.swimlane and Template.listsGroup each built the same jQuery UI
sortable for their row of lists - options, placeholder sizing, dragscroll
pause and the autorun that follows the drag-handle and permission changes -
two copies of about fifty lines. initListsSortable builds it once and, as
listsGroup already did, replaces a sortable already on the element.
The guards that counted three list sortables count the shared one and check
that both templates call it; each is still gated by canModifyBoard().
Thanks to xet7 !
Received, Start, Due and End were each written twice in cardDate.js, once
for the opened card (card*Date) and once for the minicard (minicard*Date):
the same field, the same colouring, the same title and the same editor,
eight copies of about 40 lines. CARD_DATE_KINDS describes each date once
and both templates are built from it; only the due date counts down, as
before. cardDate.js goes from 596 to 410 lines.
The tests that matched each template's own helpers block read the
CARD_DATE_KINDS entry instead, and still fail if Received, Start or End
gains the countdown.
Thanks to xet7 !
The members widget and the add/remove organization and team popups of the
Right sidebar each built the same onCreated state by hand - error and
loading flags, a paged subscription and setError/setLoading/isLoading - five
copies of about 25 lines. initGroupPickerState builds it once; each picker
keeps the publications it subscribed to before.
Thanks to xet7 !
#5729's fixed (same) width for all lists is read by list.js and turned on
from the list header menu in listHeader.js, and each carried the same five
localStorage/profile helpers. They are client/lib/fixedListWidth.js now,
with the same storage keys, so a viewer's saved choice still applies.
tests/fixedListWidthShared.test.cjs fails if a client file grows its own
copy again.
Thanks to xet7 !
Without an account the browser keeps the board view, and client/lib/utils.js
had two hand-written copies of the view list for it: setBoardView stored
any of them, but storedBoardView's copy lacked Product Backlog, Sprints,
Sprint Report and Velocity, so those opened as Swimlanes after the reload.
Both now ask isKnownBoardView, i.e. BOARD_VIEWS, which removes the two
lists (about a hundred lines) and the drift with them.
The guards that counted view names in utils.js check the isKnownBoardView
calls instead, and boardViewMenu fails if a hand-written list returns.
Thanks to xet7 !
- models/cards.js defined assignMember ... toggleAssigner twice in one
Cards.helpers({}); in an object literal the last key wins, so the first
twelve methods (about a hundred lines, with their own linked-board
branches) never ran. models/users.js had two copies of the swimlane-height
storage helpers, and the header's event map an empty
'click .js-open-bookmarks' the real handler replaced. Each first copy is
removed; what runs is unchanged.
- An Admin Panel attachment settings pane: attachmentSettings.jade (four
templates) and about 300 lines of settingBody.js for S3, transfer limits
and storage paths. No menu opened it and no template drew it; Admin Panel /
Attachments (attachments.js) is the one in use.
- Templates drawn by nothing: dateBadge, dateCustomField and
cardCustomFieldDate with its JS, orgAvatar and teamAvatar, the three
*ModalTitle templates of My Cards, Due Cards and Global Search, boardLists,
boardCreateRulePopup, addMemberPopupTest, and subtasksItems with the two
templates only it used.
- CSS repeated word for word: accessibility.css opened with a copy of
myCards.css's first 74 lines; list.css repeated its 768px collapsed-list
block at 1024px and 1200px, which 768px already covers; boardHeader.css
repeated its .mobile-mode rules inside a 801px media query. The later
copies decided nothing, so the result is the same.
tests/noDuplicateObjectKeys.test.cjs parses every app and package file and
fails on an object literal that defines a key twice, and
tests/templatesAreRendered.test.cjs fails on a template nothing draws,
apart from runtime-built names and three listed exceptions. Tests that read
the removed copies now read the code that runs.
Thanks to xet7 !
getMembersToMap (models/wekanmapper.js) is async, and cloneBoard kept its
Promise and called membersMapping.forEach on it, so every clone through
that method stopped with "membersMapping.forEach is not a function". The
board menu clones with copyBoard, so this was the cloneBoard method only.
WekanCreator carried an identical getMembersToMap that nothing called; it
is removed, leaving the one in wekanmapper.js.
tests/asyncResultAwaited.test.cjs resolves, file by file, every variable
assigned from an async function - declared there or imported async - and
fails when it is then used as an array or object without await.
Thanks to xet7 !
- Three snap set lines named keys the snap does not have, which snapd
stores without a word: MONGO_OPLOG_URL (the key is mongo-oplog-url),
oauth-enabled (oauth2-enabled) and "snap set wekan set disable-mongodb".
tests/docsSnapKeysExist.test.cjs checks every snap set in the docs
against the KEY_ names in snap-src/bin/config.
- Adding-new-Snap-settings-to-code.md used setting names that do not
exist (LDAP_ENABLED, LDAP_SEARCH_FILTER, a wrong KEY_ line), the edge
branch and Travis CI. It now follows the real config and wekan-help
entries, says that a login setting goes through the Admin Panel catalog
and authEnv(), and lists the other platform files a setting belongs in.
- The LDAP and Traefik Docker examples ran MongoDB 3.2/4.0 with
--smallfiles, which MongoDB 4.2 removed, and WeKan images from 2018. They
use mongo:7 and ghcr.io/wekan/wekan:latest; Traefik stays at v1.7, since
that guide's labels and traefik.toml are v1 syntax.
- The Windows page no longer sets RICHER_CARD_EDITOR, which WeKan does not
read, and points to build.bat for building from source.
Thanks to xet7 !
The notifications drawer subscribed to 'notificationsAttachments', while the
server publishes 'notificationAttachments'. Meteor reports nothing for an
unknown subscription, so it never became ready and an attachment named in a
notification was never sent to the drawer.
Six template keys did not exist in en.i18n.json, so the page showed the key
itself: the Due group of the sidebar filter ("dueAt"), the Scrum sprint form
("end", "no-cards", "custom-field"), the Jira estimate field on import, the
passkey trash icon ("remove") and two webhook URL fields ("URL"). Each now
uses the existing key with that wording, so every language that already
translates it shows it translated.
tests/subscriptionsArePublished.test.cjs checks every subscribe() in the
tree against every publication, and tests/templateI18nKeysExist.test.cjs
every literal key of every template against en.i18n.json.
Thanks to xet7 !
A feature's guide now sits under the menu it is used from, read from the
templates rather than from where somebody once put it: Header-Bar,
All-Boards, Pages, Board-Header (search and every Board View), Board-Sidebar
(Board Settings, Filter, Members, Labels, Archive), Rules, Swimlanes, Lists,
Cards, Member-Settings, Admin-Panel and Login-Page. Topics used from several
menus (editor, history, theme, translations, email, troubleshooting) are in
General.
- Each area has a README mapping its menu entries to their guides and to the
template or server module that implements them. Board-Header/README.md
lists all 40 Board View entries with the template boardBody.jade renders;
five views (Table, Calendar Mode, Time, Timeline, Map) have no guide of
their own yet and say so.
- Board-View.md listed 25 views while BOARD_VIEWS has 40; it now lists all
of them with their English labels.
- Pages keep their file names. Where a released changelog links an old path,
a "# Moved" page stays there and points to the new one; test-menu.sh skips
folders holding only those.
- Undo was filed under Login; it is General/Undo now.
- Every link in docs, code comments, scripts and tests follows the move;
docsLinksResolve checks them all.
Tests that read a doc follow it to its new place. boardViewSettings now fails
when Board-View.md or Board-Header/README.md differs from BOARD_VIEWS, or
names a template boardBody.jade does not render.
Thanks to xet7 !
Found by following every import, re-export and require from client/main.js
and server/main.js (meteor.mainModule: only what that reaches is loaded) and
each local package's package.js:
- packages/wekan-fullcalendar/fullcalendar/ (88 files) and update.sh: the
vendored FullCalendar v3 and the script that updated it as a git
submodule; the package takes FullCalendar 5 from npm (Npm.depends).
- packages/wekan-fontawesome/fontawesome-free: js/, sprites/, metadata/,
attribution.js and the .min.css copies; package.js adds only the plain CSS
and the webfonts. Its package.json no longer names the removed files.
- models/lib/propagateMembers.js and models/lib/teamBoardMemberSync.js:
replaced on 2026-10-14 by models/lib/groupBoardMembership.js (memberships
that record their team or org, #4178), which the server uses. Their tests
exercised the old modules; the same scenarios now run against planGain /
groupChanges, and the wiring checks are kept.
- models/lib/recoveryPlan.js and its test: a model of the SQLite recovery
policy that nothing ran; releases/ferretdb/sqlite-recovery.mjs carries it
out and tests/sqliteVerifiedRecovery.test.cjs tests that. The Recovery
design page says so.
- packages/wekan-ldap/server/configResolver.js: a vendored copy nothing
imported - the LDAP package reads its settings through the app's
accessor. Its sync test is now a check that no package carries such a
copy; three comments that sent readers to it explain the reason instead.
docs/Design/TeamsOrganizations.md also said losing a team removes nothing;
since #4178 a team takes back what it gave.
Thanks to xet7 !
package.json sets meteor.mainModule (since 2026-03-22), so the server runs
only what an import chain from server/main.js reaches. Three modules added
after that were never imported, so they never ran, while their tests - which
read the file - passed:
- server/loginTimingNormalization.js: the DDP `login` half of the LockoutBleed
timing fix (GHSA-2g94-9x3m-hv37). Only the REST login was equalized, so the
web login's response time still told whether a username exists.
- server/cardRecurrenceSchedule.js: the hourly job behind a card's
Recurrence setting; the setting was saved and nothing ever recurred.
- server/productNameCache.js: the Admin Panel product name for the snap's
pages shown while the database is down.
They are imported in server/imports.js beside their related modules. The
Meteor internals the login handler uses (Accounts._loginHandlers,
_checkPasswordAsync, _findUserByQuery) exist in this Meteor release.
tests/serverModulesLoaded.test.cjs follows the import chain from
server/main.js and fails when any server module that registers something at
load time - a startup hook, methods, publications, a cron job, a login
handler or a route - is not reached; it fails on the old imports.js.
Thanks to xet7 !
A custom role from Admin Panel -> People -> Roles reaches every board view
through the same two questions as a built-in role: what the member may do is
asked of the capability table, which answers from the custom role, and which
cards they see is asked of the member's flags, which the custom role writes.
fallbackRoleFor() picks only among built-in roles that see no more cards, so
a role whose "Sees all cards" is off always mirrors an assigned-only role and
one with it on never does. The flags are written when a member gets the
role, rewritten on every board when the role is saved, replaced with the
custom role by setMemberPermission, and kept when the role is deleted; no
other code writes them.
tests/boardRoleViews.test.cjs builds a member for all 32 capability
combinations as the server writes it and checks that the card scope, the
card rule, REST reads and writes, button rules and the real Email View
selector narrow them exactly when the role does not see all cards, and that
nothing sets a role flag without the custom role following.
server/lib/tests/customBoardRoles.tests.js turns "Sees all cards" off and on
for a role in use and checks its member becomes assigned-only and back
(meteor test; not run here). docs/Features/Members/Roles.md explains it in
"Custom roles in every board view".
Thanks to xet7 !
docs/Features/Members/Roles.md gains "Roles in every board view": a role is
the member's on the board, not the view's, so every view, the API and every
write ask the same code. A table names each of the 40 board views and the
server source it reads - the board publication, the table page, Scrum, board
status, activities, the Email and Chat views - and what an assigned-only
member gets from it; the board report charts are refused to them, as exports
are, because they are computed from every card. Then search, All Boards,
notifications, position and change history, exports, REST and writes, what
is recorded in Admin Panel -> Problems, and the tests that keep a new view
from skipping the rule. The old "Which cards they see" paragraph, which
named only the board publications, points to it.
Thanks to xet7 !
AssignedBleed follow-up, GHSA-r4gm-mjj9-g3x8. An assigned-only member
(isNormalAssignedOnly, isCommentAssignedOnly, isReadAssignedOnly, and custom
roles through those flags) sees only the cards assigned to them. The Board
Email View checked board.isVisibleBy() alone, so it published the title,
description and email metadata of every card in a list or label, counted
them, and searched them. It now merges assignedOnlyCardScope into its folder
and search selector for the page and the count, and a reply on another card
is refused.
An audit of every path that returns or changes a card found the same shape -
"a member of the board, so every card of it" - elsewhere, all fixed here:
- reads: the board publication's ancestor cards, linked source cards with
their subtasks and dependency targets, and comment reactions; All Boards
tile counts; a board's activities from linked boards; the Chat view and
the All Boards feed, which also let card MEMBERS through (assignees is the
rule); My Attachments' card; GET /api/user/cards; position history of a
card; the cross-board subtask picker; Scrum rollover counts; and the
notification publications, which kept sending the full documents of cards
named in old notifications - also to members removed from the board;
- writes on a card the member cannot see: the canonical
canEditCardOrLinkedCard / editableCardIds (every Cards.allow update,
checklists, items, attachments, text notes, custom fields, the shared
description, card methods) now ask the card - the role's `write` is a
board answer, and the card is read when a caller passed it without
assignees; DDP and REST comments, button rules on a card, checklist moves,
issue state, watching, the card-opened webhook, moveBoardObjects,
moveSwimlane and attachment moves;
- REST writes: checkBoardWriteAccess refuses assigned-only members, as
checkBoardAccess already does for reads - those routes act on any card,
list or swimlane by id. The GHSA-f396 canary still fires for a role
without the write capability.
A refusal no view would produce is recorded under AssignedBleed in Admin
Panel -> Problems (authz.assigned, medium: never disables); opening a view,
searching and REST calls are narrowed or refused without a record.
tests/boardRoleViews.test.cjs runs the Email View selector, pins every path
above, and fails when any server file that serves card data has no role
rule and no stated exemption; the "assignee or card member" rule is
rejected anywhere in server/ and models/. server/lib/tests/editableCardIds
adds an assigned-only case for both edit checks (meteor test; not run here).
Seven guards that pinned the old lines now pin the new ones.
Thanks to crypto-nidh, binary-lover and xet7 !
2026-10-16 13:03:34 +03:00
2546 changed files with 246645 additions and 386082 deletions
echo "::error::SANDSTORM_KEYRING secret is not set. meteor-spk pack signs the .spk with the WeKan Sandstorm app's private key, which lives in this keyring, so packing cannot proceed without it. Add the base64 of your ~/.sandstorm-keyring file as the SANDSTORM_KEYRING repository secret (e.g. 'base64 -w0 ~/.sandstorm-keyring')."
echo "::error::SANDSTORM_KEYRING secret is not set. spk pack signs the .spk with the WeKan Sandstorm app's private key, which lives in this keyring, so packing cannot proceed without it. Add the base64 of your ~/.sandstorm-keyring file as the SANDSTORM_KEYRING repository secret (e.g. 'base64 -w0 ~/.sandstorm-keyring')."
exit 1
fi
@@ -3789,29 +3754,17 @@ jobs:
# idempotently with --clobber replacing the same asset.
SPK="wekan-${VERSION}-sandstorm.spk"
# WHERE THE GIGABYTE IS — reported AFTER pack, because pack is what creates
# the trees to measure.
#
# v10.93 failed here with "App exceeds uncompressed size limit of 1 GiB" and
# nothing else, so v10.94 added a size report BEFORE `meteor-spk pack`. It
# printed nothing at all: `meteor-spk pack` BUILDS the Meteor app itself
# ("Building Meteor app..." comes after it starts), so .meteor-spk/deps and
# .meteor-spk/bundle do not exist until it has run. The report was measuring
# two directories that were not there yet.
#
# So the sizes are taken afterwards — on failure, which is when they are the
# whole point, and on success, where the headroom against 1 GiB is worth
# knowing before the next thing is added. `du -shL` DEREFERENCES: deps is a
# symlink to the meteor-spk.deps tree build-deps.sh assembles, and plain
# `du -sh` on a symlink reports the link, not the gigabyte behind it.
# WHERE THE GIGABYTE IS. Sandstorm refuses an app over 1 GiB
# uncompressed across .meteor-spk/deps and .meteor-spk/bundle, and
# v10.93 failed on exactly that with nothing else said - so the trees
# are measured after the build, on failure and on success. `du -shL`
# DEREFERENCES: deps is a symlink to the meteor-spk.deps tree.
sizes() {
for tree in .meteor-spk/deps .meteor-spk/bundle; do
# PACK AGAIN WITHOUT REBUILDING. `meteor-spk pack` runs the Meteor
# build first - the "Building Meteor app..." line - which
# regenerates .meteor-spk/bundle and throws the trim away. v10.98's
# log is the proof: the trim took the bundle 856M -> 424M, which
# with the 336M deps tree is 760M, comfortably under the limit, and
# then pack rebuilt it and failed at the same size as before.
#
# An earlier note here claimed pack REUSES the bundle. It does not.
# That was inferred from a size reading taken right after the prune
# - this same point in the sequence - and never checked against what
# the second pack actually did.
#
# Sandstorm's own `spk pack` does only the packing: it reads
# sandstorm-pkgdef.capnp, whose sourceMap searchPath is exactly the
# two trees that have just been trimmed, and signs with
# ~/.sandstorm-keyring. So the retry uses that where it exists.
SPK_BIN=""
for candidate in /opt/sandstorm/latest/bin/spk /opt/sandstorm/spk \
"$(command -v spk 2>/dev/null)"; do
if [ -n "$candidate" ] && [ -x "$candidate" ]; then SPK_BIN="$candidate"; break; fi
done
set +e
if [ -n "$SPK_BIN" ]; then
echo "--- packing the TRIMMED trees with $SPK_BIN (no rebuild)"
"$SPK_BIN"pack "$SPK"
pack_rc=$?
if [ "$pack_rc" -ne 0 ]; then
echo "--- spk pack failed (exit $pack_rc); falling back to meteor-spk, which rebuilds"
meteor-spk pack "$SPK"
pack_rc=$?
fi
else
echo "::warning::no sandstorm spk binary found, so this retry uses meteor-spk, which REBUILDS the bundle and undoes the trim above."
meteor-spk pack "$SPK"
pack_rc=$?
fi
set -e
["$pack_rc"-eq 0 ] && echo "--- the trimmed bundle packed"
fi
fi
if [ "$pack_rc" -ne 0 ]; then
echo "::error::meteor-spk pack failed (exit $pack_rc). The sizes above are what it tried to pack; Sandstorm's limit is 1 GiB uncompressed across .meteor-spk/deps and .meteor-spk/bundle together."
echo "::error::the Sandstorm .spk did not pack (exit $pack_rc). The sizes above are what it tried to pack; Sandstorm's limit is 1 GiB uncompressed across .meteor-spk/deps and .meteor-spk/bundle together."
echo "::warning::Attempt $i failed on Launchpad infrastructure (an HTTP 5xx, a timeout or a dropped git/TLS transfer), not on WeKan; allowing up to $attempts attempts."
fi
if [ "$i" -lt "$attempts" ]; then
# Clear a half-pushed clone only when submission never reached
# monitoring. A failed poll must keep the running build intact.
@@ -5286,8 +5204,8 @@ jobs:
echo "Clearing snapcraft's remote-build cache before retrying submission."
rm -rf "$HOME/.cache/snapcraft/remote-build"
fi
echo "Retrying in 60s..."
sleep 60
echo "Retrying in ${pause}s..."
sleep "$pause"
fi
done
echo "::error::Remote build ${MATRIX_ARCH} failed after $attempts attempts (no wekan_${VERSION}_${MATRIX_ARCH}.snap produced). The Launchpad build log printed above says why; the builds are also at https://launchpad.net/~xet7/+snaps" >&2
@@ -5776,7 +5694,7 @@ jobs:
# rewrites the notes of a release that is already published.
timeout-minutes:15
steps:
# ref: main, so this job reads the same CHANGELOG.md the release job did.
# ref: main, so this job reads the same CHANGELOG-big.md the release job did.
echo "::error::SANDSTORM_KEYRING secret is not set. meteor-spk pack signs the .spk with the WeKan Sandstorm app's private key, which lives in this keyring, so packing cannot proceed without it. Add the base64 of your ~/.sandstorm-keyring file as the SANDSTORM_KEYRING repository secret (e.g. 'base64 -w0 ~/.sandstorm-keyring')."
echo "::error::SANDSTORM_KEYRING secret is not set. spk pack signs the .spk with the WeKan Sandstorm app's private key, which lives in this keyring, so packing cannot proceed without it. Add the base64 of your ~/.sandstorm-keyring file as the SANDSTORM_KEYRING repository secret (e.g. 'base64 -w0 ~/.sandstorm-keyring')."
exit 1
fi
@@ -113,7 +115,11 @@ jobs:
set -euo pipefail
STAMP="$(date +'%Y_%m_%d-%H_%M_%S')"
SPK="wekan-sandstorm-${STAMP}.spk"
meteor-spk pack "$SPK"
# The Sandstorm package profile, the linux/x64 sockjs trim and a
# `spk pack` of the trimmed trees (not `meteor-spk pack`, which would
# rebuild the bundle and undo the trim). The script restores
# .meteor/packages and .meteor/versions when it ends.
bash releases/sandstorm-slim-spk.sh "$SPK"
# Report the packed size and warn if it exceeds Cloudflare's 100 MB upload
# cap (a proxied browser upload to a Cloudflare-fronted Sandstorm 413s above
# this; install via a DNS-only host / SSH tunnel, or trim further).
@@ -26,7 +26,7 @@ Work out which you are from the git identity (the next section says how), then:
| Who | Where the work goes |
| --- | --- |
| **The maintainer** — `Lauri Ojansivu <x@xet7.org>` | **Directly on `main`.** Never a feature branch, never a topic branch, never a pull request. |
| **A contributor** — anybody else | **A branch in your own fork, then a pull request** for the maintainer to review. Never a commit on `main`. |
| **A contributor** — anybody else | **A branch in your own fork, then one pull request per feature or fix** for the maintainer to review. Never a commit on `main`, and no changelog changes. |
Nothing else is a third option. If a tool, a task runner or an agent harness offers
some other arrangement — a topic branch for the maintainer, a direct push for a
@@ -35,13 +35,15 @@ contributor — that offer is the mistake, not the rule.
The maintainer's half is the release flow, not taste. Releases here are frequent —
several a day when a fault is being chased — and `releases/release-all.sh` cuts one
from whatever is on `main`, taking the version from the `# Upcoming WeKan ® release`
section of `CHANGELOG.md`. Work parked on a branch is work that is not in the next
section of `CHANGELOG-big.md`. Work parked on a branch is work that is not in the next
release, and `tests/changelogEntriesBelongToTheirRelease.test.cjs` fails when a
released section links a commit that release does not contain.
The contributor's half is review. Nobody but the maintainer commits to `main` in
wekan/wekan, so a change from anyone else arrives as a pull request — which is also
the only place it can be discussed before it lands.
the only place it can be discussed before it lands. That is why each pull request
holds one feature or one fix, and leaves the changelogs to the maintainer: see
already exists, on <https://wekan.fi/sponsors>, and belongs nowhere else. The **publishing / release
steps** below are available to a human maintainer only: an AI stops after
committing locally and never runs a step that pushes or publishes.
**Maintainer mode is the only mode that commits directly and the only mode that
updates changelogs**, in WeKan and in the maintainer's repositories under
`.tools/` alike. It applies to the changes the WeKan maintainer makes, and to the
changes of an agent working as the maintainer according to this file.
- **Contributor mode** — the identity is somebody ELSE, in a fork or a clone of your
own — a person, or an AI that files pull requests on its own account. Then: do
**not** commit to `main` and do **not** run any release/publishing step. Make the
changes on a branch and open a **pull request** for the maintainer to review. The
"commit as Lauri Ojansivu", "commit directly to `main`", and all release
instructions below are **maintainer-only and do not apply to you**.
**not** commit directly to the repository or to `main`, and do **not** run any
release/publishing step. Make the changes on a branch and open a **pull request**
for the maintainer to review. The "commit as Lauri Ojansivu", "commit directly to
`main`", changelog and release instructions below are **maintainer-only and do
not apply to you**.
Every pull request, to wekan/wekan or to a maintainer's repository under `.tools/`,
follows two more rules:
- **One feature or one fix per pull request.** Changes share a pull request only
when they are related to that one feature or fix. Anything unrelated, however
small, goes in a pull request of its own, so each can be reviewed, merged or
declined without holding up the others.
- **No changelog changes.** Do not edit `CHANGELOG-big.md`, `CHANGELOG.md`, or the
`CHANGELOG.md` or `CHANGELOG` of a repository under `.tools/` — not an Upcoming
entry, not TODO Later. Describe the change in the pull request instead. The
maintainer writes the entry after merging, when there is a commit for it to link.
Who that pull request credits follows the attribution rule above, and the two
cases differ:
@@ -171,8 +190,8 @@ The one repository under `.tools/` that is somebody else's is `sandstorm-io/sand
cloned for reference only.
Everything below marked as maintainer-specific (committing directly, the exact commit
author, and the entire "Making a release" / publishing flow) applies only in maintainer
mode. The rest (translation policy, CHANGELOG structure, tests, validating from code) is
author, updating changelogs, and the entire "Making a release" / publishing flow) applies
only in maintainer mode. The rest (translation policy, tests, validating from code) is
good practice for everyone.
## Process: "Fix open issues"
@@ -206,6 +225,9 @@ When asked to fix an open issue (one issue at a time):
Fixes #1234,
```
A contributor instead commits on a branch for one pull request that fixes this
one issue, with no changelog changes (see "Contributor mode" above).
6. **If the bug is already fixed**, still add a commit that CLOSES the issue (a commit
whose message ends `Fixes #1234,`), noting where/when it was fixed.
@@ -340,7 +362,7 @@ directly after the merge.
the two patch repositories under `.tools/` — node-patches and mongo-tools-patches.
- Directory structure:
- `wekan` — this repo (https://github.com/wekan/wekan); see
`docs/DeveloperDocs/Directory-Structure.md`; `CHANGELOG.md` at root. It is at
`docs/DeveloperDocs/Directory-Structure.md`; `CHANGELOG-big.md` and `CHANGELOG.md` at root. It is at
`~/repos/wekan` on Linux, `~/Documents/repos/wekan` on macOS and
`Downloads\repos\wekan` on Windows. The maintainer commits on `main`; a
contributor works on a branch and opens a pull request — see "Where this
@@ -407,6 +429,10 @@ directly after the merge.
### CHANGELOG
- **[maintainer only] Changelogs are updated only in maintainer mode.** This whole
section is for the maintainer and for an agent working as the maintainer, in WeKan
and in the repositories under `.tools/`. A pull request contributor changes no
changelog in any of them (see "Contributor mode" above).
- **Every repository writes its changelog in the format that repository's own file
already uses.** Open its changelog, read the entries above the place you are
adding one, and match them — never import another project's shape into it. The
@@ -414,7 +440,7 @@ directly after the merge.
| Repository | File | Format |
| --- | --- | --- |
| `wekan/wekan` | `CHANGELOG.md` | the WeKan format this section describes: `# Status` (Platforms/Version/TODO Later nested as `<details>`), then `# v<MAJOR>.<MINOR> YYYY-MM-DD WeKan ® release` sections of `<details>` entries whose `<summary>` links the commit |
| `wekan/wekan` | `CHANGELOG-big.md` (and the generated `CHANGELOG.md`, below) | the WeKan format this section describes: `# Status` (Platforms/Version/TODO Later nested as `<details>`), then `# v<MAJOR>.<MINOR> YYYY-MM-DD WeKan ® release` sections of `<details>` entries whose `<summary>` links the commit |
| `wekan/node-patches` | `CHANGELOG.md` | the same WeKan format, with `# Upcoming node-patches release` |
| `wekan/mongo-tools-patches` | `CHANGELOG.md` | the same WeKan format, with `# Upcoming mongo-tools-patches release` |
| `wekan/FerretDB` | `CHANGELOG.md` | **upstream FerretDB's** format, not WeKan's: `## [v1.48.0](tag URL) (YYYY-MM-DD)` and `### New Features 🎉` / `### Fixed 🐛` / `### Other Changes 🤖` bullets ending `by @xet7. Thanks to xet7.` |
@@ -426,6 +452,52 @@ directly after the merge.
out and would break the tooling that parses them. When this file and the file being
edited disagree, **the file being edited wins** — and everything below in this
section is about the WeKan format specifically.
- **WeKan has TWO changelogs, and only one is written.** `CHANGELOG-big.md` is
the changelog: `# Status`, `# Upcoming WeKan ® release` and every release in
full, in the `<details>` format below - every rule in the rest of this section
is about it, and every entry, fix and release goes into it. `CHANGELOG.md` is
the SHORT changelog, generated from it by `node releases/changelog-short.mjs`
and never edited by hand:
| | `CHANGELOG.md` |
| --- | --- |
| Status, TODO Later | none |
| First | the unreleased `# Upcoming WeKan ® release`, when there is one: its **In short** paragraph only, then `[All changes so far](CHANGELOG-big.md#upcoming-wekan--release)` |
| Below it | the five newest `# v<MAJOR>.<MINOR> YYYY-MM-DD WeKan ® release` |
| Per release | its **In short** paragraph only, then `[All changes in v…](CHANGELOG-big.md#anchor)` |
| Markup | plain Markdown: no `<details>`, `<summary>`, `</summary>` or `</details>`, and every HTML link a Markdown link |
It exists because the big file is for whoever writes and audits a release,
and most readers - GitHub's front page, the Sandstorm App Market
(`sandstorm-pkgdef.capnp` embeds it), the Helm chart's changelog link - want
what changed lately, at a glance. `releases/release-all.sh` regenerates it
right after naming the new release, so it is in the same release commit, and
`releases/changelog.sh` does the same; `tests/changelogShort.test.cjs` fails
when it is not exactly what the generator writes. So: write
`CHANGELOG-big.md`, run the generator, commit both - and never edit
`CHANGELOG.md` to fix something, because the next run overwrites it.
- **"Update wekan changelog" means both files, in this order:**
1. Find what is not in the changelog yet: `git log` since the newest commit
`CHANGELOG-big.md`'s Upcoming section (or, when it has none, the newest
release's `Prepare v… release` commit) already covers, by anybody - a
merged pull request and the maintainer's own commits alike.
2. Check whether a release was cut since the last update (`release-all.sh`
renames Upcoming to `# v<NEW> …` and opens no new one). If there is no
@@ -26,7 +26,7 @@ Work out which you are from the git identity (the next section says how), then:
| Who | Where the work goes |
| --- | --- |
| **The maintainer** — `Lauri Ojansivu <x@xet7.org>` | **Directly on `main`.** Never a feature branch, never a topic branch, never a pull request. |
| **A contributor** — anybody else | **A branch in your own fork, then a pull request** for the maintainer to review. Never a commit on `main`. |
| **A contributor** — anybody else | **A branch in your own fork, then one pull request per feature or fix** for the maintainer to review. Never a commit on `main`, and no changelog changes. |
Nothing else is a third option. If a tool, a task runner or an agent harness offers
some other arrangement — a topic branch for the maintainer, a direct push for a
@@ -35,13 +35,15 @@ contributor — that offer is the mistake, not the rule.
The maintainer's half is the release flow, not taste. Releases here are frequent —
several a day when a fault is being chased — and `releases/release-all.sh` cuts one
from whatever is on `main`, taking the version from the `# Upcoming WeKan ® release`
section of `CHANGELOG.md`. Work parked on a branch is work that is not in the next
section of `CHANGELOG-big.md`. Work parked on a branch is work that is not in the next
release, and `tests/changelogEntriesBelongToTheirRelease.test.cjs` fails when a
released section links a commit that release does not contain.
The contributor's half is review. Nobody but the maintainer commits to `main` in
wekan/wekan, so a change from anyone else arrives as a pull request — which is also
the only place it can be discussed before it lands.
the only place it can be discussed before it lands. That is why each pull request
holds one feature or one fix, and leaves the changelogs to the maintainer: see
already exists, on <https://wekan.fi/sponsors>, and belongs nowhere else. The **publishing / release
steps** below are available to a human maintainer only: an AI stops after
committing locally and never runs a step that pushes or publishes.
**Maintainer mode is the only mode that commits directly and the only mode that
updates changelogs**, in WeKan and in the maintainer's repositories under
`.tools/` alike. It applies to the changes the WeKan maintainer makes, and to the
changes of an agent working as the maintainer according to this file.
- **Contributor mode** — the identity is somebody ELSE, in a fork or a clone of your
own — a person, or an AI that files pull requests on its own account. Then: do
**not** commit to `main` and do **not** run any release/publishing step. Make the
changes on a branch and open a **pull request** for the maintainer to review. The
"commit as Lauri Ojansivu", "commit directly to `main`", and all release
instructions below are **maintainer-only and do not apply to you**.
**not** commit directly to the repository or to `main`, and do **not** run any
release/publishing step. Make the changes on a branch and open a **pull request**
for the maintainer to review. The "commit as Lauri Ojansivu", "commit directly to
`main`", changelog and release instructions below are **maintainer-only and do
not apply to you**.
Every pull request, to wekan/wekan or to a maintainer's repository under `.tools/`,
follows two more rules:
- **One feature or one fix per pull request.** Changes share a pull request only
when they are related to that one feature or fix. Anything unrelated, however
small, goes in a pull request of its own, so each can be reviewed, merged or
declined without holding up the others.
- **No changelog changes.** Do not edit `CHANGELOG-big.md`, `CHANGELOG.md`, or the
`CHANGELOG.md` or `CHANGELOG` of a repository under `.tools/` — not an Upcoming
entry, not TODO Later. Describe the change in the pull request instead. The
maintainer writes the entry after merging, when there is a commit for it to link.
Who that pull request credits follows the attribution rule above, and the two
cases differ:
@@ -171,8 +190,8 @@ The one repository under `.tools/` that is somebody else's is `sandstorm-io/sand
cloned for reference only.
Everything below marked as maintainer-specific (committing directly, the exact commit
author, and the entire "Making a release" / publishing flow) applies only in maintainer
mode. The rest (translation policy, CHANGELOG structure, tests, validating from code) is
author, updating changelogs, and the entire "Making a release" / publishing flow) applies
only in maintainer mode. The rest (translation policy, tests, validating from code) is
good practice for everyone.
## Process: "Fix open issues"
@@ -206,6 +225,9 @@ When asked to fix an open issue (one issue at a time):
Fixes #1234,
```
A contributor instead commits on a branch for one pull request that fixes this
one issue, with no changelog changes (see "Contributor mode" above).
6. **If the bug is already fixed**, still add a commit that CLOSES the issue (a commit
whose message ends `Fixes #1234,`), noting where/when it was fixed.
@@ -340,7 +362,7 @@ directly after the merge.
the two patch repositories under `.tools/` — node-patches and mongo-tools-patches.
- Directory structure:
- `wekan` — this repo (https://github.com/wekan/wekan); see
`docs/DeveloperDocs/Directory-Structure.md`; `CHANGELOG.md` at root. It is at
`docs/DeveloperDocs/Directory-Structure.md`; `CHANGELOG-big.md` and `CHANGELOG.md` at root. It is at
`~/repos/wekan` on Linux, `~/Documents/repos/wekan` on macOS and
`Downloads\repos\wekan` on Windows. The maintainer commits on `main`; a
contributor works on a branch and opens a pull request — see "Where this
@@ -407,6 +429,10 @@ directly after the merge.
### CHANGELOG
- **[maintainer only] Changelogs are updated only in maintainer mode.** This whole
section is for the maintainer and for an agent working as the maintainer, in WeKan
and in the repositories under `.tools/`. A pull request contributor changes no
changelog in any of them (see "Contributor mode" above).
- **Every repository writes its changelog in the format that repository's own file
already uses.** Open its changelog, read the entries above the place you are
adding one, and match them — never import another project's shape into it. The
@@ -414,7 +440,7 @@ directly after the merge.
| Repository | File | Format |
| --- | --- | --- |
| `wekan/wekan` | `CHANGELOG.md` | the WeKan format this section describes: `# Status` (Platforms/Version/TODO Later nested as `<details>`), then `# v<MAJOR>.<MINOR> YYYY-MM-DD WeKan ® release` sections of `<details>` entries whose `<summary>` links the commit |
| `wekan/wekan` | `CHANGELOG-big.md` (and the generated `CHANGELOG.md`, below) | the WeKan format this section describes: `# Status` (Platforms/Version/TODO Later nested as `<details>`), then `# v<MAJOR>.<MINOR> YYYY-MM-DD WeKan ® release` sections of `<details>` entries whose `<summary>` links the commit |
| `wekan/node-patches` | `CHANGELOG.md` | the same WeKan format, with `# Upcoming node-patches release` |
| `wekan/mongo-tools-patches` | `CHANGELOG.md` | the same WeKan format, with `# Upcoming mongo-tools-patches release` |
| `wekan/FerretDB` | `CHANGELOG.md` | **upstream FerretDB's** format, not WeKan's: `## [v1.48.0](tag URL) (YYYY-MM-DD)` and `### New Features 🎉` / `### Fixed 🐛` / `### Other Changes 🤖` bullets ending `by @xet7. Thanks to xet7.` |
@@ -426,6 +452,52 @@ directly after the merge.
out and would break the tooling that parses them. When this file and the file being
edited disagree, **the file being edited wins** — and everything below in this
section is about the WeKan format specifically.
- **WeKan has TWO changelogs, and only one is written.** `CHANGELOG-big.md` is
the changelog: `# Status`, `# Upcoming WeKan ® release` and every release in
full, in the `<details>` format below - every rule in the rest of this section
is about it, and every entry, fix and release goes into it. `CHANGELOG.md` is
the SHORT changelog, generated from it by `node releases/changelog-short.mjs`
and never edited by hand:
| | `CHANGELOG.md` |
| --- | --- |
| Status, TODO Later | none |
| First | the unreleased `# Upcoming WeKan ® release`, when there is one: its **In short** paragraph only, then `[All changes so far](CHANGELOG-big.md#upcoming-wekan--release)` |
| Below it | the five newest `# v<MAJOR>.<MINOR> YYYY-MM-DD WeKan ® release` |
| Per release | its **In short** paragraph only, then `[All changes in v…](CHANGELOG-big.md#anchor)` |
| Markup | plain Markdown: no `<details>`, `<summary>`, `</summary>` or `</details>`, and every HTML link a Markdown link |
It exists because the big file is for whoever writes and audits a release,
and most readers - GitHub's front page, the Sandstorm App Market
(`sandstorm-pkgdef.capnp` embeds it), the Helm chart's changelog link - want
what changed lately, at a glance. `releases/release-all.sh` regenerates it
right after naming the new release, so it is in the same release commit, and
`releases/changelog.sh` does the same; `tests/changelogShort.test.cjs` fails
when it is not exactly what the generator writes. So: write
`CHANGELOG-big.md`, run the generator, commit both - and never edit
`CHANGELOG.md` to fix something, because the next run overwrites it.
- **"Update wekan changelog" means both files, in this order:**
1. Find what is not in the changelog yet: `git log` since the newest commit
`CHANGELOG-big.md`'s Upcoming section (or, when it has none, the newest
release's `Prepare v… release` commit) already covers, by anybody - a
merged pull request and the maintainer's own commits alike.
2. Check whether a release was cut since the last update (`release-all.sh`
renames Upcoming to `# v<NEW> …` and opens no new one). If there is no
please do. The maintainer of WeKan then requests the CVE through that GHSA, and
your Hall of Fame row is upgraded from 3 stars to 5.
- **CVE Policy**: WeKan Security requests CVE at GitHub when releasing Security Advisory.
### 3. Post-Release and Public Disclosure:
@@ -264,13 +272,13 @@ Meteor.startup(() => {
On the Sandstorm platform, Standalone WeKan features like the Admin Panel are turned off using environment variables, because Sandstorm platform provides SSO for all apps running on Sandstorm.
[Sandstorm](https://sandstorm.io) is a separate Open Source platform that has been
[security audited](https://sandstorm.io/news/2017-03-02-security-review) and found bugs fixed.
[Sandstorm](https://sandstorm.org) is a separate Open Source platform that has been
[security audited](https://sandstorm.org/news/2017-03-02-security-review) and found bugs fixed.
Sandstorm also has passwordless login, LDAP, SAML, Google, and other auth options already.
On Sandstorm, code is read-only and signed by app maintainers; only grain content can be modified.
WeKan on Sandstorm runs in a sandboxed grain; it does not have access elsewhere without a user-visible PowerBox request or opening a randomly-generated API key URL.
Also read [Sandstorm Security Practices](https://docs.sandstorm.io/en/latest/using/security-practices/) and
python3 api.py importboard EXPORT.json # Import a whole board (with rules/workflows) from a WeKan export
python3 api.py migratefromwekan REMOTE_URL REMOTE_USER REMOTE_PASS # Import ALL boards+workflows+rules from another WeKan
python3 api.py exportboardpdf BOARDID OUTPUT.pdf # Export a whole board to PDF
python3 api.py importboardfrom SOURCE FILE [--csv-mapping MAPPING.json] # Import a tool's export file (csv and excel: --csv-mapping says which column is which field); SOURCE is a key of the import page (trello, jira, csv, excel, planner, wrike, notion, ...), see docs/Features/ImportExport
python3 api.py importboardfrom SOURCE FILE [--csv-mapping MAPPING.json] # Import a tool's export file (csv and excel: --csv-mapping says which column is which field); SOURCE is a key of the import page (trello, jira, csv, excel, planner, wrike, notion, ...), see docs/Features/Board-Sidebar/Board-Settings/Import-Export
python3 api.py exportboardformat BOARDID FORMAT OUTPUTFILE # Export to a tool's format; FORMAT is a key of the export menu (kanboard, trello, planner, wrike, wrikeworkflow, ...)
python3 api.py importboardsfrom SOURCE [--split] FILE_OR_DIR ... # Import many boards: each file (or each file of a directory or .zip) becomes a board; --split makes one board per project
python3 api.py exportallboards FORMAT OUTPUTFILE [--boards ID1,ID2] # Export all boards: Excel as one workbook with a sheet per board, other formats as a .zip with a file per board
if[ -n "$(git status --porcelain -- CHANGELOG.md 2>/dev/null)"];then
git add CHANGELOG.md
if[ -n "$(git status --porcelain -- CHANGELOG-big.md CHANGELOG.md 2>/dev/null)"];then
git add CHANGELOG-big.md CHANGELOG.md
git commit -q -m "CHANGELOG: repoint commit links after history moved.
A rebase rewrote the commits these entries link to, so the links named commits
@@ -2601,7 +2645,7 @@ choose() {
# snap/bundle, not on a maintainer's machine) and the superseded old-*.sh and
# translations/fill_translations.py.
# Release notes summarize translations as affected languages; other changelog
# details stay in CHANGELOG.md. Notes include only In short, Security,
# details stay in CHANGELOG-big.md. Notes include only In short, Security,
# Translations language list, thanks and the changelog link. Binary provenance remains
# a build artifact and must not be appended by any release entry point.
RELEASE_SCRIPTS=("Release|Release All: audit, commit, push and build with Actions|releases/release-all.sh|||"
@@ -2917,7 +2961,8 @@ while [ -z "$opt" ]; do
"Count tests by category|Count amount of tests by category"\
"Run all FerretDB tests - SEQUENTIAL|Run all FerretDB tests - SEQUENTIAL: unit, vet and the integration suite of the FerretDB subdirectory of this repo, one at a time, logs in log/<datetime>/"\
"Live tests|Run live tests against real services with your own accounts: GitLab write-back with the mirror's glab login (only scratch items it creates and removes) and the API imports, read only (releases/live-tests/README.md); a service with no login here is skipped, log in .tools/log/<datetime>/"\
"All databases (sequential)|Test all databases that have a Docker image for this CPU, SEQUENTIALLY: build newest FerretDB v1 from source, then run every FerretDB v1 query type against each database and compare that they all answer the same (results in log/<datetime>/)";;
"All databases (sequential)|Test all databases that have a Docker image for this CPU, SEQUENTIALLY: build newest FerretDB v1 from source, then run every FerretDB v1 query type against each database and compare that they all answer the same (results in log/<datetime>/)"\
"Database speed test|Time the common WeKan queries on every database that has a Docker image for this CPU, SEQUENTIALLY, with twice a production WeKan of dummy data; check they all answer the same and write docs/Databases/FerretDB/1/SpeedTest/Results.md";;
"Tools")
choose "Tools"\
"Save Meteor deps list|Save Meteor dependency chain to ../meteor-deps.txt"\
@@ -2971,15 +3016,20 @@ for _once in 1; do
;;
*)echo"Unsupported Linux distribution; install a C/C++ toolchain, git, curl, wget, 7zip, zip, unzip, file and npm." >&2;exit1;;
# No shell rc file is edited: build.sh and the releases/ scripts put
# .tools/nvm's Node.js and .tools/.meteor on PATH themselves
# (releases/tools-env.sh).
# Meteor as https://docs.meteor.com/about/install.html installs it:
# `npx meteor`, as this user, never sudo (see the Linux branch).
if[ -x "$HOME/.meteor/meteor"];then
echo"Meteor is already installed in $HOME/.meteor"
# `npx meteor`, as this user, never sudo, with HOME=.tools for the
# installer only (see the Linux branch).
if[ -x "$WEKAN_TOOLS_DIR/.meteor/meteor"];then
echo"Meteor is already installed in $WEKAN_TOOLS_DIR/.meteor"
else
npx -y meteor
HOME="$WEKAN_TOOLS_DIR"npx -y meteor
fi
exportPATH="$HOME/.meteor:$PATH"
. "$WEKAN_DIR/releases/tools-env.sh"
exit;
elif[["$OSTYPE"=="cygwin"]];then
# POSIX compatibility layer and Linux environment emulation for Windows
@@ -3370,6 +3414,12 @@ for _once in 1; do
./releases/db-conformance.sh
;;
"Time the common WeKan queries on every database that has a Docker image for this CPU, SEQUENTIALLY, with twice a production WeKan of dummy data; check they all answer the same and write docs/Databases/FerretDB/1/SpeedTest/Results.md")
Loaded 100 of 2546 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.