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 !
- B324 docs/Features/Login/ldap-sync/ldap-sync.py: the MD5 of an LDAP photo
only notices a changed photo, so it says usedforsecurity=False.
- B202 tests/nextcloud/bootstrap.py: the downloaded WeKan release zip is
extracted only after every member is checked to land inside the target
(zipfile also refuses '..' and absolute names; now it is explicit).
- B102 tests/backfill-charts-pagination.py: exec of this repository's own
backfill-charts.sh planner under test, in an empty namespace with the
network patched out - kept, and marked with its reason.
- B108 tests/release-telemetry.py: '/tmp/check-telemetry.py' is a fake
__file__ for the path logic; nothing is written there - marked.
- B106 tests/ucsRelease.test.py: the fake portal's expected value is a test
fixture, passed through a variable rather than as a password literal.
bandit reports none of the five now; the three tests pass.
Thanks to Qlty and xet7 !
Qlty (zizmor) security findings in the workflows that build and publish
every release:
- template-injection (128): ${{ ... }} pasted into a run: script is expanded
before the shell sees it, so a crafted workflow_dispatch input, tag or step
output becomes shell code. 251 expressions in 13 workflows now reach their
scripts through the step's env: and are used as quoted shell variables
($env:NAME in the PowerShell steps); ${{ env.X }} is plain $X. Where a
value sat in single quotes it is double-quoted now, and six unquoted ones
are quoted.
- unpinned-uses (20 in Qlty, 160 with current zizmor): every action is
pinned to a commit, with its version as a comment for Dependabot.
actions/dependency-review-action@v5 was a branch; it is v5.0.0.
- excessive-permissions (8): AppImage, Flatpak, mac, windows,
release-all and release-all-missing grant contents: read at the workflow
level, and each job that writes (a release asset, a git push, the docker
push, gh run rerun) is granted that on the job. Nothing used actions:
write except recover, which keeps it.
- artipacked (47 -> 13): actions/checkout gets persist-credentials: false,
except in the jobs that push with the checkout's credentials, directly or
through releases/version.sh, release-website.sh, release-charts.sh and
backfill-charts.sh.
actionlint with shellcheck reports 50 notes, the 56 before minus the six
quoted. Every PowerShell step parses (pwsh 7.6.7). Not run on GitHub.
tests/workflowHardening.test.cjs fails on an expression in a run script,
an unpinned action, a workflow-level write grant in a multi-job workflow,
or a checkout keeping credentials outside the pushing jobs, and runs
zizmor when it is installed. Eleven guards that pinned the old text, or ran
the extracted scripts with the expressions substituted, now pin the env
form and pass the values as the step's environment.
Thanks to Qlty and xet7 !
AssignedBleed follow-up, GHSA-3cgr-47wm-9cc9. An assigned-only member
(isNormalAssignedOnly, isCommentAssignedOnly, isReadAssignedOnly) sees only
the cards assigned to them. 1a218ead02 applied that to the attachment API,
but six other paths asked only for board membership, which such a member
has:
- the /cdn/storage/attachments and /cfs/files/attachments downloads
(universalFileServer isAuthorizedForBoard) served any card's file;
- Attachments.protected, ostrio:files' own download check, likewise;
- the legacy /cfs/files/attachments route likewise;
- searchAttachmentDocumentText matched the extracted text of every card's
PDF/DOCX/XLSX/PPTX on the board and returned snippets with their
attachment and card ids - no id needed, and it supplied the ids for the
downloads;
- legacyBoardAttachments without a cardId published every legacy
attachment record of the board;
- linkedListCards published every card of the source list.
The downloads now also ask mayReadBoardAttachment(), which names the
refusing path in Admin Panel -> Problems (authz.assigned, medium: never
disables). The document search and the legacy publication are narrowed to
the member's cards (assignedOnlyCardIds); asking the publication for a card
not assigned to them is refused and recorded, as card details always pass
the open card. Linked-list cards take the source board's
assignedOnlyCardScope; searching and viewing a linked list are ordinary
use, so those are not recorded.
The board's own files - background (sidebar or API upload) and map image -
carry no card and are part of the board every member sees; they stay
readable, where the attachment API refused them to an assigned-only
member. Any other card-less file is still refused.
tests/assignedOnlyReads.test.cjs pins each path, the board-file rule and
the refusal source, and fails when any file in server/ or models/ that
serves attachment or document data lacks the rule without a stated
exemption. The Playwright spec checks both downloads refuse the hidden
card's file but not the member's own, that the search finds only the
member's card, and that the owner is unaffected. Four source guards that
pinned the old lines now pin the new ones.
Thanks to crypto-nidh and xet7 !
The snap runs Caddy 2, and migrate-caddyfile-v1-to-v2.sh treats a Caddyfile
that `caddy adapt` rejects as Caddy v1: it moves it to Caddyfile.v1.bak-<n>
and, when it cannot convert it, writes the default in its place - on every
start. 19 Caddyfile examples in the docs were Caddy v1 (`proxy / localhost:3001
{ websocket transparent }`, `browse`, `fastcgi`, `tls self_signed`, `ext`,
v1 `log`/`errors`, `root /path`), so a file copied from the snap install page
was renamed each time it was put back. All are now Caddy 2 (reverse_proxy,
file_server browse, php_fastcgi, tls internal, try_files, log output) and pass
`caddy adapt` with Caddy 2.11.7. Also:
- `http://Xhttps://X { tls ... }` became `X {`: Caddy 2 refuses TLS on the
http address and redirects to https by itself;
- the Windows examples `load` the certs folder - Caddy 2's load takes folders;
- the Docker Meteor3 Caddyfile gave the Let's Encrypt and CloudFlare variants
of one site the same host, which Caddy 2 refuses as ambiguous; each has its
own host now;
- Caddy.md's logs example and its systemd unit, installer and setcap are
Caddy 2 (`caddy run --config`, no getcaddy.com, no -conf/-agree/-email).
The fallback template the migration writes proxied to localhost:3000. With
Caddy enabled the snap runs WeKan on caddy-bind-port (default 3001), so a
migrated site pointed at nothing. Both templates now read the snap settings
from caddy-control's environment - `:{$CADDY_PORT:8080}` and `reverse_proxy
localhost:{$CADDY_BIND_PORT:3001}` - so `snap set wekan port=...` reaches
Caddy on the restart the configure hook already does; that hook's sed lines
only ever matched Caddy v1 lines. The commented TLS example in the template
had `protocols h1` inside a site and a stray `}`, so uncommenting it failed;
it uses `alpn http/1.1` now.
tests/caddyfileExamplesV2.test.cjs finds every Caddyfile in the docs and the
snap, fails on Caddy v1 syntax and on a Caddy 1 command line, checks the
templates' ports, and with a Caddy 2 binary runs `caddy adapt` on all 30.
snapStagePackages.test.cjs pinned the wrong port 3000 and now pins 3001.
Thanks to Group-Deed and xet7 !
After the 12.47 fix, #6771 comment 6197931378 showed the background sync
running and importing nobody: "filter": "(&(&(objectclass=person))(uid=\2a))",
"Search result count 0". importNewUsers() asked for every user with the string
'*', and the RFC 4515 escaping added for login names on 2026-09-27
(679a8b3499) turned the wildcard into a literal asterisk. The wildcard is now
reachable only through ALL_LDAP_USERS (userSearchValue.js), a Symbol that a
login request cannot carry, so a typed '*' is still escaped.
The sync also ran far more often, or less often, than set: later.js is a
calendar, and every(n).minute() is "minutes 0, n, 2n ... of each hour", an
interval only when n divides 60. 2 hours and 1 day both became hourly, 90 s
became every minute, and 3600 (seconds, typed as milliseconds) every 4
seconds. Milliseconds now become an interval later keeps exactly - seconds
and minutes dividing 60, hours dividing 24, whole days - pinned to the start
of the unit; anything else is rounded to the nearest one with a warning, and
an interval under a minute is warned about as probably seconds.
tests/ldapImportAllUsers.test.cjs reproduces the reported filter and checks
the wildcard, the still-escaped typed '*', and that no code searches the
string '*'. tests/ldapSyncSchedule.test.cjs checks every interval up to 36
hours reaches later only as an every(n) it keeps, and with the real later.js
that 1 min ... 1 week keep their spacing.
Thanks to tromlet and xet7 !
The NetBSD x64 bundle of v12.47 stopped in npm install: argon2's
native build ran node-gyp, which looks for `python3` or `python`, and
pkgsrc's python313 installs only /usr/pkg/bin/python3.13 - "You need to
install the latest version of Python". OpenBSD's package provides
python3, so only NetBSD failed.
releases/bsd/build-bundle.sh now, when neither is on PATH and PYTHON is
not set, hands node-gyp the versioned interpreter it finds
(/usr/pkg/bin/python3.N, skipping python3.N-config) through PYTHON and
npm_config_python, before the npm install; with no Python at all it
stops with an ::error:: naming the package. Simulated locally with a
pkgsrc-shaped directory and no python3 on PATH; with python3 present
nothing changes. tests/releaseScriptsUtf8.test.cjs pins the lookup and
its place before the install.
Thanks to xet7 !
All three Windows bundle builds of v12.47 stopped at the risk audit:
"Automated risk indicators found: server/lib/localizeAvatar.js: new URL
origin http://127.0.0.1". The file had not changed. risk-audit.py read
its baseline with a bare read_text(), which on a Windows runner decodes
cp1252, so the baseline's "http://127.0.0.1/…" (a comment in that file)
became "http://127.0.0.1/…" and no longer matched the source. With a
non-UTF-8 default on Linux (LC_ALL=C, -X utf8=0) the old script misread
the baseline the same way and counted all 16688 files as changed; the
fixed one reads it as the build does.
Every text read and write in releases/*.py now names UTF-8: the risk
audit, the telemetry check, remote-release.py (which also reads and
rewrites CHANGELOG.md, with its ®), the dependency, UCS, Nextcloud, Bun,
Mac and Snap scripts, and the GITHUB_ENV/GITHUB_OUTPUT writes.
tests/releaseScriptsUtf8.test.cjs fails on any read_text(), write_text()
or text-mode open() in releases/ without an encoding, multi-line calls
included; tests/bun-release.py follows the checksum write's new
argument. Every Python and node test of these scripts passes.
Thanks to xet7 !
- sidebarArchives.js imports its own template: cardDetails.js now
imports it for restoreArchivedCard, and a file that registers on a
template it does not import may run before the template exists
(clientBundleImports).
- The 6.09 restore spec makes its test ObjectIds with crypto.randomBytes,
not Math.random (codeqlInsecureRandomness).
- Tigre's new restore strings say ወረቀት ካርድ for card and ምዱድ for board,
the file's own words, not the bare Tigrinya ካርድ and ሰሌዳ.
Thanks to xet7 !
Restoring an archived card whose list had reached a hard WIP limit did
nothing at all: the Restore handlers checked canBeRestored() and, when it
said no, returned without a word or a method call, and Restore all
skipped such cards just as quietly.
- Restore, in the Archive sidebar and in an open archived card, now goes
through one function (restoreArchivedCard): a list that is full - or
gone - opens the restore-to-list popup, which says the list has reached
its WIP limit and offers only lists with room; with none left it says
so instead of offering a list that would fail again.
- Restore all restores every card that has room and then says how many
it left out and why. Its popup opens against the link kept before the
loop: after the awaits the browser has cleared evt.currentTarget.
- canBeRestored() decides with models/lib/wipRestore.js, which also
blocks a list that is OVER its hard limit: the old `===` let a card
into a list whose limit had been lowered below its count. A soft limit
still only warns.
Tests: tests/wipRestore.test.cjs (the rule, over-limit and soft/disabled
negatives, both Restore paths, Restore all's count, and a scan that no
client code skips a blocked restore without a word);
restore-wip-limit.e2e.js against the production bundle (restore into
another list with the full one not offered, Restore all restoring what
has room and naming the rest, a soft limit restoring at once), 12 of 12
over three repeats with the existing archive restore test. The four new
strings are translated in every locale.
Thanks to jullbo and xet7 !
Fixes#6772.
"[uncaughtException] WeKan is stopping: TypeError: string.toLowerCase is
not a function" from later.parse.text, called by wekan-ldap's sync
schedule, with the snap restarting in a loop - the report on Alma Linux
8.10 with LDAP sign-in. A numeric LDAP_BACKGROUND_SYNC_INTERVAL reached
later's text parser as a Number; 7dfd942ff6 reads milliseconds or
later.js text and never throws, and tests/ldapSyncSchedule.test.cjs
reproduces this exact error with the real later.js.
Thanks to tromlet and xet7 !
Fixes#6771.
The upgrade report (one board with attachments loading forever after a
6.09 to 10.85 dump and restore) came without the board's data. The
fixture tests/fixtures/restored/wekan-6.09-board.json is that board's
SHAPE instead, derived from `git show v6.09:models/...`: CollectionFS
attachments (cfs.attachments.filerecord with its GridFS file and chunk),
a card cover pointing at a filerecord, lists with swimlaneId '', members
without the newer role flags, customFields by boardIds, checklists,
comments and activities as 6.09 wrote them.
restored-609-board.e2e.js restores it into the test database as it is
and requires the board to load: the board publication becomes ready with
the board and its cards, and the page shows its lists and cards, not the
spinner or the "could not be loaded" message. It passes against the
production bundle - and also against a bundle built without the client
cover fix, so neither the 6.09 data shape nor that fault is what hung the
reported board; what did is particular to that board's data, which the
board's page now names through the server log.
Thanks to xet7 !
Found running the Email view's browser test against the production
bundle: once in eight runs, searching for text that matches nothing still
listed the previous search's card. Both views subscribe in an autorun, so
a new search's subscription starts before the old one stops, and both
published their page's ids into ONE shared index document; DDP's merge
box shows the older subscription's value while both exist, so whenever
the old one stopped late the new search showed the old rows.
Each request now has its own index, keyed by its arguments
(models/lib/reportPageIndex.js reportPageKey), built the same way on the
server and the client (emailViewPageArgs normalizes the Email view's
folder, page and search), and each view reads only the index of the
request it made. Admin Panel / Problems stops its subscription before the
next one, so its requests never overlap and keep a fixed id.
Tests: tests/reportPageKey.test.cjs (one id per request, server and
client agree, no autorun view reads a shared id); emailView and
publicBoardsPage pin the keyed ids. Against a rebuilt bundle the Email
view test passed 8 of 8, and with the Public Boards, Chat, Version
Control and import specs 30 of 30.
Thanks to xet7 !
Run against a freshly built bundle, as build.sh's browser run does, two
faults in the spec itself showed:
- Email view: Compose sends real mail, but the spec started no mail sink,
so Send answered "The email could not be sent" in any run without an
SMTP server. It catches the mail locally now, as the other mail specs do
(helpers/smtpSink.js on WEKAN_TEST_SMTP_PORT), and checks that the
message reached the recipient under its subject.
- Chat view: after page.reload() the new connection signs in again from
the login cookie, and openView() called setBoardView before that, so on
a fast server it failed with "Must be logged in". It waits for the
sign-in, and a refused call names its code and reason instead of the
minified "r".
Against the bundle: the Email, Chat, Version Control attachments and
import Attachment files specs pass (Chat 3 of 3 repeated, Email 5 of 5).
Thanks to xet7 !
Running the new browser tests showed it: the rename popup appended
this.extensionWithDot, which a migrated attachment can lack, so the file
became "architectureundefined". It falls back to the extension now.
The browser tests were run against a local source-mode server: the
Version Control attachments spec (which now asks for the storage paths
as an administrator, for that call only) and the import Attachment files
spec pass; the Chat channels spec expects no unread message once the card
was opened in the middle, which reads it.
Thanks to xet7 !
"[uncaughtException] WeKan is stopping: TypeError: string.toLowerCase is
not a function at later.parse.text ... at Object.schedule
(wekan-ldap/server/sync.js)", in a snap restart loop. The docs give
LDAP_BACKGROUND_SYNC_INTERVAL in milliseconds, settings_get() turns a
numeric value into a Number, and the schedule handed it straight to
later's text parser, which threw from a timer. Text later could not read
did not throw, but made a schedule that never ran - as a quoted
'every 1 mins' from a compose file did, the second report in #2354.
packages/wekan-ldap/server/syncSchedule.js reads the setting and never
throws: milliseconds (a Number or digits) become that interval, later.js
text is read as before, one pair of surrounding quotes is removed, unset
keeps the original default (recur().on(0).minute()), and anything
unreadable logs why and uses the default. The docs, the snap description
and the compose files say both forms work, instead of "leave this unset
so it does not crash".
Tests: tests/ldapSyncSchedule.test.cjs reproduces the crash with the real
later.js from the build, schedules 3600000 ms an hour apart and 10000 ms
ten seconds apart, reads quoted text, keeps the default, never throws on
any value a setting can hold, and fails if any server file hands a setting
to parser.text directly.
Thanks to B2RN and xet7 !
Fixes#2354.
The guards that failed before this release's translation work, each
decided by which side is right:
- The locale was wrong: Bislama's email recovery strings say sanem, the
word the guard pins; Arabic-script Uzbek writes "e" as ې (26 words);
Romansh says "chaschuna ina perdita da datas", names its own Menu and
uses one word for the template container; Tigre's card is ወረቀት ካርድ,
never the bare Tigrinya ካርድ (six strings); Wolaytta's permanent-delete
label still held English words, now in the file's own Wolaytta; and
Tamazight's operator-assignee is the portable search keyword again -
the 2026-10-13 label sweep had translated a parser keyword, so that
correction is removed and the earlier one stands.
- The guard was stale: Acehnese settled on kad for card on 2026-10-14,
so the example cards use the file's own word; Uzbek key names may be
translated as long as the printed label stays beside them; the README's
coverage count is 234 now; the Transifex pull test hands its own node to
the script it runs.
- The last English values of nine locales are their own words or settled
terms (Neapolitan and Volapük "No", Frisian and Flemish "is", Breton
"Me", Walloon "Pause", Venetian "Sprint", Veps "minus"), recorded as
reviewed shared words, so the completeness guards and --missing agree:
0 languages with untranslated strings.
Thanks to xet7 !
Keyboard key names, Blockly's math names, Scrum roles and the S3 and
webhook labels, which the earlier passes had kept in English: the
established locales translate them (Finnish Välilyönti, German
Leertaste), so these do too. Breton keeps the reviewed "Me", Walloon the
reviewed "Pause", Venetian its "Sprint", and Veps writes minus without
the Finnish long vowel. Low confidence for the key names of Nahuatl,
Buryat, Cherokee, Klingon, Tibetan, Dzongkha, Wolaytta and Tigre.
What stays equal to English is the language's own word (Neapolitan
"No", Frisian "is", Volapük "if"), or in Blockly an operating system,
sin/cos/tan, and key labels the language's keyboards print.
Thanks to xet7 !
Greenlandic, Kashmiri, Nahuatl, Tigre, Tibetan, Dzongkha, Oromo,
Wolaytta, Northern Sami, Yakut, Inuktitut and Cherokee: every value still
equal to English, translated directly from each language's existing
wording, placeholders checked against English, only English placeholders
filled. Low confidence for Nahuatl, Wolaytta, Yakut, Inuktitut and
Cherokee; a speaker's Transifex translation replaces any of them.
Thanks to xet7 !
Translated directly from each language's existing wording, placeholders
checked against English, only English placeholders filled: Tamazight,
Volapük, Acehnese, Wolof, Fijian, Guarani, Veps, Quechua, Chichewa,
Northern Ndebele, Tigrinya, Samoan, Aymara (its last string, the S3
bucket, too), Fula, Bambara, Tswana, Kirundi, Swati, Chuvash and Buryat.
Low confidence for Volapük, Veps, Buryat and Tamazight; a speaker's
Transifex translation replaces any of them. The Veps guard records "ei
%1" for Blockly's "not": Veps negates with ei, as Finnish does.
Thanks to xet7 !
Every value still equal to English in these locales, translated directly
from each language's existing wording and kanban terms, Blockly's
editor messages included; placeholders checked against English before
anything was written, and only English placeholders filled, so no human
translation is replaced.
All of Klingon, Ewe, Walloon, Luganda, Tongan, Tsonga, Venda, Kinyarwanda,
and the remaining strings of Bashkir, Pashto, Cantonese, Igbo, Xhosa,
Bhojpuri, Romansh, Maori, Breton, Sorani, Faroese, Northern Sotho,
Asturian, Amharic, Akan, Kyrgyz, Sicilian, Burmese, Hausa, Yoruba,
Frisian, Kurdish, Cornish, Uyghur, Aromanian, Sinhala, Sindhi, Manx,
Neapolitan, Assamese and their regional variants. Low confidence for
the constructed and smallest of them (Klingon, Venda, Tongan, Manx,
Cornish, Aromanian); a speaker's Transifex translation replaces any of
them. Walloon writes tangent as tandjinte, not the Waray-shared French.
Thanks to xet7 !
customBoardRoles pinned Chat's comment check on the open board; the Chat
view opens a card of any board now, so it asks the card's board, and
the guard says so. liveTestsNoSecretLogs built a RegExp escaping only
`$`; it escapes every metacharacter (incompleteEscaping).
Thanks to xet7 !
The five strings in every locale, translated directly from each
language's existing wording; English variants keep English. Bosnian
uses tabla for board and Tigre its established word for name, as their
guards require.
Thanks to xet7 !
An upgrade report: after a 6.09 to 10.85 dump and restore, one board with
attachments showed only the loading animation. The data is not
available, but the code had three faults on that path:
- The board page waited for its publication to be ready and had no
onStop, so a publication that stopped with an error left the spinner
up forever. It now shows "This board could not be loaded", pointing at
the server log, which names the cause.
- The client cache fell back to the legacy (CollectionFS) attachment
lookup, an async stub on the client: its Promise was taken for an
attachment, so a 6.09 card's coverId became a cover whose link()
threw, and the plural lookup handed callers a Promise for an array.
The client no longer calls it; the server cache awaits the real one.
- The legacy attachments publication called startsWith() on a record's
type, which throws when a 6.09 record holds something other than a
string; it is read as a string.
Tests: boardRefreshReadiness drives the error path (shown, cleared on
another board, ignored from a board left behind); legacyBoardLoad pins
that no client cache calls an async lookup and that the server awaits
every one. The board itself is not reproduced: that needs the data.
Thanks to xet7 !
Their exports name attachments without carrying the files, so the files
were reported as a loss. Each service documents where a file is
downloaded, behind the same sign-in as its API:
- Jira: fields.attachment[].content (/rest/api/3/attachment/content/<id>),
Basic email:API token; Jira's redirect to its media store is followed
without the sign-in.
- Deck: /index.php/apps/deck/api/v1.0|v1.1/boards/../stacks/../cards/..
/attachments/<id>, Basic user:app password, OCS-APIRequest: true.
- OpenProject: _links.downloadLocation, relative to the instance, Basic
apikey:<key>.
- Asana: GET /attachments/<gid>?opt_fields=download_url with a personal
access token, then the signed download_url without it; a file the
export embeds (export_boards.pl) is taken as it is; one kept on Google
Drive and the like is a link, reported.
- GitLab: a private project's /uploads/ link through
/api/v4/projects/<id>/uploads/<secret>/<file> (17.4+), Bearer token.
The import page asks for the address, user and token under "Attachment
files" for these five (the REST import takes the same as attachmentAuth).
They go with that one import: held beside the creator in a WeakMap, never
stored or logged, the token field emptied on Import, and sent ONLY to
the origin the member typed - an export names any URL it likes. Every
download is the import's SSRF-guarded fetch, which validates each hop
and drops Authorization on a cross-origin redirect. A file that cannot
be fetched is on the loss report with its reason. Unticking Attachments
leaves them out (Jira's per-issue field is pruned too).
Zenkit is left out: it publishes no way to download a file.
Tests: importAttachmentAuth.test.cjs (each service's request, the
sign-in only to its origin, malformed sign-ins, never stored or
logged), the parser tests pin the attachment lists, a Meteor test
imports an embedded Asana file onto its card and shows a Jira file is
reported without a sign-in and that the sign-in never reaches an address
the guard refuses, and import-attachment-auth.e2e.js the page (not run
here). Not run against the live services.
Thanks to xet7 !
The Attachments tab listed each file as a link to a new browser tab. A
file the attachment viewer can show now opens there on a click on its
thumbnail or name, with next and previous walking the tab's files, and
every file has the controls of an opened card's attachments: view,
download, rename, delete (the soft delete, behind the same confirmation)
and the cover / background menu, the writes for the same members as the
card's gallery.
Thanks to xet7 !
Jira's sprint report has no documented API, so a closed sprint was
reported and skipped. It is now imported as closed, with its start and
close snapshots rebuilt from documented issue data: the Sprint field,
status and resolution at the sprint's start and completion dates, from
each issue's change history (expand=changelog), or from the issues as
they are now when there is none. The snapshots are marked partial. A
closed sprint with no start or completion date is still reported.
Thanks to xet7 !
- Clicking a card in the channel tree took the member to that card's
board. It now opens the card in the middle pane with its conversation
beside it, whatever board it is on, and the page stays where it is. The
card's conversation comes from a publication of its own
(chatViewCardComments) with the tree's limits - a board the member sees,
a role that reads comments, only their cards on an assigned-only board -
and its writers' names; the comment box follows the card's own board.
- Folding the Channels or Comments pane left only an icon: the double
caret stays now, pointing the way the pane opens, mirrored with the
others right to left.
Tests: chatView.test.cjs (no navigation, the subscription, the card's
board for commenting, the publication's limits, the carets),
email-chat-views.e2e.js (another board's card in the middle without
leaving the page, carets visible on folded panes).
Thanks to xet7 !
It was recorded as impossible: Jira signs in only with an Authorization
header, and the bridge proxy takes that header for the grain's grant. But
Sandstorm's HTTP driver can keep the sign-in in the grant itself
(shell/imports/server/drivers/external-ui-view.js in the Sandstorm
source): the member connects the Powerbox to "any address" and enters the
Jira address with the credentials in it - https://EMAIL:API-TOKEN@site
(the e-mail's @ as %40), or https://jira.example.org#TOKEN for a Server /
Data Center token - and Sandstorm moves them into the grant and adds
Authorization: Basic (or Bearer) to every request the grain sends through
it. WeKan never sees them.
- egress.mjs syncApiRoot grants a Jira list at its own address, and the
Powerbox request asks for Basic authentication
(ApiSession.PowerboxTag.authentication; apiSessionDescriptor takes it).
- In a grain the Jira fetcher sends no Authorization of its own, and a
Jira source needs no WeKan token there: a marker credential says the
grant holds the sign-in. Only for Jira - GitLab sends its token in the
query, where a marker would reach GitLab.
- The List Sync popup tells a Jira list in a grain what to enter in the
Powerbox, translated in all locales; the Sandstorm docs say how, and
that the same #token form may let a private GitHub, Gitea or Forgejo
through (not tried yet).
- TODO Later drops "Not possible from a Sandstorm grain", its last
category naming an issue; changelogFormat no longer needs one.
Not run in a real grain here. Tests: sandstormSyncEgress.test.mjs (the
root and the Basic descriptor, and an address with credentials in it
still refused), sandstormJiraGrant.test.cjs (the server, the fetcher,
the marker only for Jira, the popup and the docs).
Thanks to xet7 !
GitHub CodeQL js/clear-text-logging: releases/live-tests/basecamp-import.mjs
logged the Basecamp account id read from WEKAN_LIVE_BASECAMP_ACCOUNT. The
live tests run with the maintainer's own logins and keep a log under
.tools/log/, so nothing from a login or its configuration belongs in it.
The same shape was in two more tests and is gone there too: Planka logged
its configured address, and the GitLab write-back test its GitLab address
and scratch project path, also in the message that says it is skipped.
Each now logs what it does in fixed words.
These are human-run scripts, not part of the WeKan server, so there is no
attempt for Admin Panel -> Problems to record.
tests/liveTestsNoSecretLogs.test.cjs follows every value read from the
environment, a login or the credentials into the names it lands in, and
fails on any log line of any live test that interpolates one; a negative
case pins the reported shape.
Thanks to GitHub CodeQL and xet7 !
Maintainer decision 2026-10-15:
- Translating from dictionaries and each language's own wording is
enough; speakers review it later if they have the time. The category
listing terminology for a speaker to check is removed.
- Formats with no published documentation are not taken on: Restyaboard,
YouTrack, Airtable, Leantime, Things 3, and Taskcafe (an archived
project whose GraphQL API has no documentation beyond its source).
Attachment contents from Jira, Deck, OpenProject, Asana, Zenkit and
GitHub/GitLab stay: each documents its attachment API.
Thanks to xet7 !
#4790 was a collection of ideas ("I don't know what use cases it would
cover"), and a split into six new feature requests was prepared on
2026-09-29. Checked against the code, they describe what WeKan already
does under other names, so nothing needs filing:
- labels on organizations, teams, people and boards: organizations and
teams are the groups of people; boards are grouped into All Boards
workspaces and have their colours;
- a label from an LDAP group: LDAP groups sync as organizations and teams
(#4737), with LDAP_SYNC_ADMIN_GROUPS and LDAP_SYNC_GROUP_ROLES;
- adding or keeping users out of boards by label: a board is shared with
an organization or a team, and a private board not shared with one
keeps it out;
- a board role for an LDAP group: the group syncs as a team, and the team
is shared to the board with its role;
- finer roles: custom board roles (#3022, closed);
- users by e-mail domain: done (orgAutoAddByDomain.js, #5850, #4178);
- rules by label, meteor-roles and multitenancy were thoughts in the
comments, not requests.
The prepared split (docs/Features/User-Filter-4790-Split.md) is removed,
TODO Later no longer waits on it, and docs/Design/TeamsOrganizations.md
says how its rows are covered - also that custom roles exist now.
Thanks to xet7 !
Fixes#4790.
What is built leaves the backlog, also where it is not yet tested against a
live service:
- #5758 Windows SSO: built in a594a58117 (v12.46), for a Windows host in a
domain; the handshake was not run here.
- #2698 GitLab integration: GitLab sync and import (2026-09-30), and the
write-back of edits, comments, closing and reopening in d6c1d6fce1
(v12.46), with a live test in build.sh / build.bat -> Tests -> Live
tests; GitLab login to WeKan through OAuth2 is documented, and card
numbers with board keys give the #number references.
- #337 accessibility: move buttons, focus trap, landmarks, labels and
every icon-only control named and guarded; a screen-reader pass is
testing, not a missing part.
- #2208 Sandstorm: everything that can work from a grain is built (email
sending, outgoing webhooks, list sync, the REST API through Sandstorm);
Jira sync from a grain cannot, and stays recorded as such.
Also out: the continuous backup's live-account tests, the SMTP, browser
and identity-provider test matrix, the UCS / Nextcloud ExApp variable
check, the History switch (decided on 2026-10-08), Zenkit's unverified
export, the translation batch history, and the importers built on
2026-10-14 (Planka, Microsoft To Do, KanbanFlow, Basecamp). Left: Jira
closed sprints, the #4790 split to file, translations and their
terminology review, the attachment-board report, the formats not read
yet, and the Sandstorm limits.
changelogFormat's count of categories that link issues follows the
smaller backlog; it still fails on an issue named without its link.
Thanks to nikalasmd, ndarilek and xet7 !
Fixes#5758,
Fixes#2698,
Fixes#337,
Fixes#2208.
- The activity feed's new stylesheet was not imported, so it would not
have been in the client bundle (clientBundleImports); client/features/
main.js imports it beside the feed's template.
- The Basecamp and Microsoft To Do importers stripped HTML tags in one
pass, which a crafted body survives ("<<b>b>" leaves "<b>"); each
removal now repeats until nothing changes (tagStrippingFixedPoint).
- renderedTitles escaped only the first "." of a field name; it escapes
every special character now (noIncompleteRegExpEscaping).
Thanks to xet7 !
The Chat view's tree listed the open board's swimlanes, lists and cards,
and every other board as a link that left for that board. Now:
- The tree is workspaces > boards > swimlanes > lists > cards, holding
the cards that have a conversation on every board the member sees
(server/chatView.js chatViewChannels). Every board without one is under
Other boards; its first message moves it up to its place.
- Each level has a caret before its icon - after it, mirrored, in a
right-to-left language - and folds open and shut with a slide (none
for a member who asked for reduced motion). Clicking a board folds it;
it no longer leaves the Chat view. The board the member is on, its
workspaces, swimlanes and lists start open; what they open or close is
kept in their browser.
- The board the member is on shows all its cards, from the board's own
data, so a conversation can start on any card and a dragged card moves
in the tree at once; its button shows only the cards with messages.
Another board opened from Other boards shows all its cards
(chatViewBoardChannels) for the same reason.
- Every level shows the messages this member has not read: others'
messages after they last opened the card, all of them on a card never
opened; opening the card - or a message arriving while it is open -
reads it.
- Every name, and the chat's header, renders as Markdown (inlineViewer).
- Only what the member may read: boards they see in All Boards, only
their own cards on an assigned-only board, no conversation where their
role reads no comments.
Tests: tests/chatView.test.cjs (the tree, Other boards, a first message
moving a board up, unread sums, the full board, carets, folding rows,
no navigation), server/lib/tests/chatViewChannels.tests.js (unread
counts, never my own, the role and assigned-only limits, a board I do
not see refused), email-chat-views.e2e.js (folding without leaving the
view, the move up, unread badges, Markdown names). New strings
translated in all locales.
Thanks to xet7 !
Titles, names and labels are Markdown in WeKan - links, emphasis, emoji -
but Board View / Version Control showed "Azure [issues](https://...)"
and the like as text, the all-boards activity feed did the same with the
cards and boards it names, and the email view's sidebar showed its lists
and labels raw ("Demo + Wishes 🌈").
inlineViewer (client/components/main/editor.jade) is the viewer itself -
Markdown, emoji, mentions, and the Admin Panel's security settings for
links and code - laid out inline at the size of the line it is in. These
views use it for every issue, milestone, label, release, wiki page and
board title, the activity sentence and its card and board, and the email
folders and row labels. An attachment's file name in its download link
stays a file name.
A rendered title can hold a link, and a link inside a link is not one:
the titles and labels that open or filter are spans with role=link now,
which Enter and Space open too, and a click on a link inside a title
opens that link instead.
tests/renderedTitles.test.cjs fails on any title-like value interpolated
as text in these views and pins the role=link spans.
Thanks to xet7 !
The all-boards activity feed showed "checked __checklistItem__ of
checklist __checklist__", "from list __oldList__ at swimlane
__oldSwimlane__", "added attachment __attachment__", an empty "at
swimlane", and raw names such as "removedChecklistItem": it filled only
the card, list and board of the notification sentence.
- server/activityFeed.js reads the ids the sentence needs and looks the
names up a batch at a time: swimlanes, old list and swimlane, checklist
and item, attachment, label, member and assignee, comment, old board.
Never a field's value (an admin-only custom field stays out), a
comment's text only for a member who may read comments, and another
board only when the member sees it too.
- A rename or a restore names its new title itself, and a swimlane's
activity keeps the swimlane's id in listId; activityTitleParams fills
those for the feed and the notifications alike.
- A placeholder nothing could fill - a deleted checklist item - is left
out instead of shown as __name__.
- Ten kinds of activity had no sentence at all, in the feed or in
notifications: removedChecklistItem, moveCardBoard, deleteBoard, the
board, list and swimlane renames and restores, joinAssignee and
unjoinAssignee. They have one now, translated in all locales; the
restores, moves and checklist ones join their notification groups.
tests/activityFeed.test.cjs fills every name, refuses comments and
hidden boards, and fails on any stored activity type without a
sentence; activity-feed-sentences.e2e.js checks the feed shows the names
and no __placeholder__.
Thanks to xet7 !
Decided on 2026-10-14. On the import page each of the six asks for what
its service needs - a self-hosted Planka its address, username and
password; the others an access token (Basecamp also its account id) -
lists what that account can import, and fetches the chosen board, list
or project. The credentials go with each call and are used for that
call's requests only; they are never stored or logged, and the page
clears secret fields once a fetch has used them. The fetched JSON fills
the import text box and the import goes on as for a pasted export: the
service's parser (models/lib/<key>ApiFormat.js) into the generalized
importer, with the members step and the loss report. The same JSON can
be pasted, or posted to POST /api/boards/import/<key>.
- server/apiImport.js: apiImportList and apiImportFetch, signed in only,
off with the Admin Panel's import switch, not in Sandstorm, rate
limited; credentials checked against the service's own fields
(models/lib/apiImportServices.js).
- server/lib/apiImportRequest.js: every request through the SSRF guard -
pinned address, no redirects, bounded in time and size - so a Planka
address cannot point the server at its own network; an administrator
can allow such a host with API_IMPORT_ALLOWED_PRIVATE_HOSTS. An error
names only the origin and the status.
- models/lib/apiImport/<key>.js: each service's client, plain Node with
the request injected, paging bounded and the token only in a header.
- Each has a docs page under docs/Features/ImportExport/, listed in the
coverage index and External-Tools.md.
Tests: tests/<key>ApiImport.test.cjs (client with a fake request, every
parsed field, wrong shapes), tests/apiImport.test.cjs (credentials, the
gate, the wiring, the shapes refused), api-import.e2e.js (the form for
every service, fetched JSON imported through the page, refusals incl. a
private address). Live tests, read only, run from build.sh / build.bat ->
Tests -> Live tests with your own accounts (releases/live-tests/README.md);
they were not run here. New strings translated in all locales.
Also: the release risk baseline allows the services' API addresses, and
the guards that count import creators, list the import sources and pin
the platform fetch() calls know the new ones.
Thanks to xet7 !
- syncHistoryAdmission pinned the line that walked successors to the
chain's tip inside appendSyncHistoryRow; 66dd9b0938 moved that walk to
historyChainTip and made the append take turns, so it pins those.
- The write-back's injectable GitLab read was a parameter named fetch,
which syncBleed's "no new platform fetch()" guard rightly could not
tell from the real one; it is readIssues now.
- syncClosedByWeKan, the card field the write-back added, is in the card
e-mail field inventory as internal provenance, excluded like the other
sync fields.
Thanks to xet7 !
Admin Panel -> Problems showed HistoryIntegrity, critical, "history-fork",
89,062 times. Each History row names the row before it, and the legacy
append (every board not migrated to the coordinated chain) read "the
newest row" and inserted after it as two separate steps: two changes
saved at the same moment on one board - two members, or one action whose
hooks record twice at once - both named the same row. Nothing was
tampered with, but the audit called it tampering, and undo, redo and
restore were refused for both rows. The writer gate around the legacy
append counts its writers for a migration; it does not make them take
turns.
- models/lib/historyLegacyAppend.js: a board's appends take turns in
this server, and each links after the chain's tip - the newest row,
then its successors, as Sync History already did - so a createdAt tie
does not fork either. The ordinary recorder, the Scrum restore writer
and Sync's legacy append all use it. Several servers writing one board
at the same moment can still race; the coordinated chain is the answer
to that.
- The hourly audit reported every broken row again every hour, which is
where 89,062 came from; each finding is now reported once
(historyIntegrityReported). It reads the History board by board instead
of all at once, and a fork of two intact rows saved within a minute of
each other is reported as what it is - integrity.historyConcurrentFork,
medium, "a History writer race, not tampering" - while anything else
stays critical.
Existing forks stay as they are (a row's hash cannot be changed), and
undo of their two rows is still refused.
tests/historyLegacyAppend.test.cjs: the old append forks under
concurrency (negative control), twenty appends at once on two boards
make two straight chains, a createdAt tie follows the chain, a failed
append does not stop the next, the audit tells a race from a changed or
late row, and no append in models/ or server/ links by "newest" without
taking turns.
Thanks to xet7 !
Board View / Email showed a card's title and description as text: "# Demo
card 👍", "**Markdown**", raw <font> tags and links, where every
other view renders them. The row's subject and preview now go through
the viewer like every card title and description in WeKan, so the Admin
Panel's settings for links and code apply to them as everywhere.
The preview was the description squeezed to one line and cut at 120
characters, which cut inside a link or a tag; it is now the start of the
message, the From line left out, cut at a line end (previewMarkdown),
and the row shows two lines of it. Headings and paragraphs in a row read
at the row's size.
tests/emailView.test.cjs pins previewMarkdown and that neither cell is
interpolated raw; email-chat-views.e2e.js checks the bold, the link and
that no ** or ]( is left in the row.
Thanks to xet7 !
#2698. A list synced from GitLab gets a Sync option of its own, off by
default: Write changes back to GitLab. With it, every Sync run first
sends what members did on the list's cards, then reads GitLab, so the
read finds both sides agreeing:
- a title or description edited in WeKan, when the issue still has the
text both sides last agreed on - the local-only edit the reconcile
already keeps. The Source footer Sync adds is not sent; when both sides
changed, nothing is written and it stays a conflict to resolve;
- a card archived in WeKan closes its open issue, and restoring a card
WeKan closed reopens it (syncClosedByWeKan); an issue GitLab closed is
never reopened by WeKan;
- a comment written in WeKan becomes a note, and keeps the note's id
(syncCommentId) so the comments sync does not bring it back twice.
Only what happens after it was switched on is sent (writeBackSince), at
most 100 writes a run. Every write is fenced by the list's Sync
reservation, goes through the same address checks as the reads (no
redirects, PUT or POST to an issue or its notes only), and records on
the WeKan side only while the card or comment is still as planned. A
refused write is shown in the Sync popup (syncWriteBackError, beside
syncSource because the run's list writes match syncSource whole) and the
read sync still runs. Not in Sandstorm, whose bridge only reads.
Tests: tests/listSyncWriteBack.test.cjs (the plan with GitLab's shapes,
the parser's footer, every negative - conflict, legacy card, old
archive or comment, foreign card, issue GitLab closed - and the wiring),
server/lib/tests/listSyncWriteBack.tests.js (end to end through
syncOneList against a fake GitLab). The live test,
releases/live-tests/gitlab-write-back.mjs, runs from build.sh / build.bat
-> Tests -> Live tests with the glab login releases/mirror.sh uses, on one
scratch issue in a private project of that account; it is human-run and
was not run here. releases/live-tests/run.mjs runs every live test and
skips a service with no login.
Also: the English variants get the SQRL and Windows sign-in strings
(their key order follows en.i18n.json); buildScriptParity accounts for
the scripts CI runs inside their own environments; new strings
translated in all locales.
Thanks to xet7 !
a539429fce made the sign-in page translate SQRL's sqrl-only and
sqrl-hardlock refusals the way it already translated
oauth-account-conflict, by one list of codes, so the guard's pin on the
old single comparison failed. It now pins the list - the conflict among
the codes, matched on err.error or the reason, shown as text - so the
behaviour it protects is the same.
Thanks to xet7 !
The Upcoming section opens with them and the Bosnian and Tsonga
terminology; TODO Later keeps only the two decided items still to build
(GitLab write-back, the API importers) and drops the finished batch
undo/redo entry.
Thanks to xet7 !
#5758. On a WeKan running on Windows in a domain, members already signed
in to Windows can sign in with "Sign in with Windows", without a
password: /sso/windows runs the browser's Negotiate handshake with
node-expose-sspi, WeKan finds or creates the member, and the browser
comes back to /sign-in with a one-time token (random, kept as its
SHA-256, one minute, used once) that the page signs in with through a
login handler - so every login check applies, deactivated accounts and
SQRL's sqrlonly included.
- WINDOWS_SSO_ENABLED=true turns it on, on Windows only.
- WINDOWS_SSO_DOMAINS lists the domains allowed; empty means nobody, and
machine accounts never.
- WINDOWS_SSO_ACTIVE_DIRECTORY=true takes name and e-mail from AD.
- WINDOWS_SSO_MERGE_EXISTING_USERS=true signs in to the account with the
same username or e-mail; otherwise a new account is made beside a
local one of the same name, since a local alice is not necessarily the
domain's alice. A Windows account signs in to the account it linked to
before.
node-expose-sspi is Windows-only and not a dependency every platform
downloads: the admin installs it into the bundle, and WeKan looks for it
there (docs/Features/Login/WindowsSSO.md; start-wekan.bat has the
variables).
Built without a Windows domain to test against, as decided: the
decisions - names, domains, profile, which member - are tested in plain
Node in tests/windowsSso.test.cjs, with negatives for every malformed or
foreign name, an empty allowlist and a same-named local account; the
handshake and the server wiring are pinned from source. New strings
translated in all locales.
Thanks to nikalasmd and xet7 !
Undoing a 10,500-card sprint close took 282 s and redoing it 578 s, about
27 ms a card: each record of a History row cost some thirty database
round trips - a read per record in the preflight, two ownership checks
(two reads each) around every read, the same again around a readback
after every write, and the whole row read back record by record twice
more at the end.
Now a row's records are read in one query per type (readTargets), and
inspectScrumHistoryWrites takes that as readMany, bracketed by one
ownership check before and one after: the worker's claim and the source
row only ever go from current to displaced, so current before and after
the read means current throughout it. Every write keeps its ownership
check right before it, after the read it is computed from; the readback
after a write confirms the value landed and leaves ownership to the next
write's fence and the batch's final readback. A sprint or release that
thousands of cards name is looked up once, not once a card. The
permission check stays per card.
The 10,500-card start / close / undo / redo test went from 14 min 27 s
to 6 min 7 s on the same machine; all 18 Scrum tests pass.
tests/scrumHistoryWriteState.test.cjs pins one read and three checks
for a bracketed read, ownership lost during it, a short answer, a missing
record and identities checked before any read (negative), and that every
write in the path is still fenced after its read.
Thanks to xet7 !
WeKan -> Rocket.Chat (OAuth2) -> Google (SAML) is three sign-ins in a row,
and with the popup login style the chain broke whenever Rocket.Chat was
not signed in yet: its SAML login left the authorize page the popup was
waiting on, so WeKan never got its code, although Google and Rocket.Chat
both logged a successful login. Logging in to Rocket.Chat first only
avoided the detour.
docs/Features/Login/OAuth2.md now says how to configure the chain: the
redirect login style (the default now; oauth2-login-style /
OAUTH2_LOGIN_STYLE / Admin Panel), Rocket.Chat's SAML entry point and
issuer, Google's NameID as the e-mail address, the exact
/_oauth/oidc redirect URI, https everywhere, how to read the logs, and the
direct alternatives (Google OIDC, WeKan's own SAML).
tests/oauth2RocketChatSamlChainDocs.test.cjs pins that redirect stays the
default in the server and the snap, and that the documented section names
the settings - and never recommends popup.
Thanks to timmy-mac and xet7 !
Fixes#6549.
The member's SQRL client sends both options with every sign-in, and WeKan
now follows them (it used to say they were not supported):
- sqrlonly: every other way of signing in is refused - password, OIDC,
LDAP, SAML, CAS, passkey, Nextcloud (validateLoginAttempt), the REST
/users/login and header login, which mint their tokens without it. A
resume token stays valid; a session already open stays open.
- hardlock: no password-reset e-mail is sent (the answer is the same as
for a sent one), a reset link sent before does not sign in, and the
member cannot change their e-mail address.
An ident records the options only once it succeeds; a flag the client
leaves out is switched off. Neither applies while the identity is disabled
from the SQRL client (the emergency stop), nor while the admin has SQRL
off, and an admin can still change the e-mail, set a password and create
a token: those are the ways back. Member Settings / SQRL shows which locks
are on; the sign-in page and the e-mail change say why they refused.
tests/sqrl.test.cjs drives both options through the protocol with a real
Ed25519 client (set, cleared, never by a query, a refused or a forged
ident), the refusal decision for every login type, and pins every path
that signs a member in - and lists every file that mints a login token,
so a new one cannot skip the check. New strings translated in all
locales.
Thanks to xet7 !
Bosnian used both ploča and tabla for a board. Every board string now uses
tabla and its cases (genitive plural tabli); ploča stays only where it
means the dashboard (kontrolna ploča) or the Admin Panel. The board
search operator abbreviation is tb, because t is already traka
(swimlane).
Tsonga used vuhlayiselo for both the Archive and storage. The Archive is
now "vuhlayiselo bya swa khale", while storage stays "vuhlayiselo" and the
database "vuhlayiselo bya data". The archive popups ask their question.
Bosnian and Croatian s3-port translated (S3 port).
The audited-corrections rows, the Bosnian audit test and the archive
filter guard follow the decision. wrongLanguageCorrections20261014 pins
Tabla, the tb abbreviation, the dashboard and Admin Panel exceptions, and
the Tsonga archive word wherever English says archive.
Thanks to xet7 !
GitLab write-back with live tests in the build menus (#2698), Windows SSO
(#5758), documenting the Rocket.Chat SAML chain (#6549), SQRL's sqrlonly
and hardlock (#2460), faster batch undo/redo, API importers for Planka,
Microsoft To Do, KanbanFlow, Basecamp, Google Tasks and Google Keep, and
the Bosnian and Tsonga terms.
Thanks to xet7 !
It timed out in every full Meteor run. Profiled at full size it passes, in
15 min 25 s: start and close 2 s, the rollover 36 s, the undo batch 282 s
and the redo 578 s - past the 15 minutes it had. The next test, which
interrupts a rollover, then met this one's unfinished writes and failed
too. The test has 30 minutes now, with the measurements beside the
limit, and the Scrum snapshot rows group passes (3 passing, 14 min).
TODO Later now records the real follow-up: batch undo/redo writes about
27 ms a card.
Thanks to xet7 !
musl, Android, FreeBSD, the Windows single files, Bun and the Quay copy,
grouped as Release builds under the bug fixes, with In short updated.
Thanks to xet7 !
v12.44's copy to Quay failed on "stream error: ... PROTOCOL_ERROR;
received from peer" - an HTTP/2 reset on the way, not a refusal. A
transport error is retried up to four times; a refusal still fails at
once. tests/releaseDockerRegistryOutage.test.cjs runs the step with a
docker that resets twice, one that refuses, and one that never recovers.
Thanks to xet7 !
- FreeBSD: sharp 0.35.5's source refuses libvips older than 8.18.7, and
FreeBSD packages 8.18.6 (latest and quarterly alike), so both FreeBSD
Bun builds stopped at that #error. A user's FreeBSD runs the packaged
libvips through the same soname whatever was compiled against, so a
patch-release gap is accepted with a notice; a larger one still stops.
- windows-x64: one HTTP 500 from the CDN failed the asset download; a
server error is now retried, a 4xx is not.
- The .sha256sum files were written in Windows text mode, CRLF, and the
windows-arm64 Bun single file then looked for "<zip>\r". They are LF on
every platform now, and the reader strips a CR from the ones already
published.
Thanks to xet7 !
The win64, win-arm64 and win32 single files stopped at once since v12.41,
for two reasons:
- win64 and win-arm64: Node.js mounts the archive at \\.\NUL\vfs\<n>, and
Meteor's mini-files.js turns a path beginning with a backslash into
C:\... - so Npm.require looked in the wrong place and the server
stopped with "Cannot find module 'denque'". The single file now carries
mini-files.js patched to keep a UNC or device path (two leading
separators) as it is; drive paths are unchanged, and the build stops if
Meteor's code is not the code the patch was written for. It also makes
a bundle runnable from a \\server\share.
- win32: the SEA blob stores its lengths as size_t, so a blob written by
the x64 Node.js on the build host is unreadable to a 32-bit node.exe
(createZipProvider got no archive). build.mjs now refuses a target whose
pointer width differs from its builder's, and single-file.yml builds
win32, i386 and 32-bit ARM with the release's i386 Node.js, checksum
verified - the i386 and ARM ones had the same fault but no runner starts
them.
tests/singleFile.test.cjs runs Meteor's path functions on Windows path
semantics before and after the patch, refuses win32 from x64, and builds
and starts a real single executable with the patched file.
Thanks to xet7 !
start-wekan.sh ran every binary through cpu-exec, a #!/bin/bash script for
Linux's /proc/cpuinfo and qemu-user. FreeBSD has no /bin/bash, so it
failed "cpu-exec: not found" and WeKan never answered (the FreeBSD
bundles since v12.41). It is now used only on Linux with a /bin/bash -
the same holds for Alpine without bash and for Termux - and the BSD
bundles drop it, as the Windows and macOS ones already do.
Thanks to xet7 !
Both builds failed every release since v12.41. sharp is an application
package (programs/server/npm/node_modules), but the scripts removed its
glibc platform packages from programs/server/node_modules, where they are
not, and the npm install there never touches the application's packages.
- musl: releases/platform-native-packages.mjs and check-addon-arch.mjs
take --libc musl: the glibc packages go and sharp's
@img/sharp-linuxmusl-<arch> and canvas's -musl package arrive,
sha512-checked. "Cannot find module 'sharp'" ended the job, from a
require() run in programs/server; it runs in programs/server/npm now.
- Android: the glibc packages go from the right directory, and sharp is
built from source against Termux's libvips in its own directory; should
that fail it runs as WebAssembly, with a warning. Termux's user does not
own the bind mount, and "chmod: ... Operation not permitted" stopped
v12.43 and v12.44 - only what is not already executable is chmodded.
tests/platformNativePackages.test.cjs installs the musl packages through
the real script; muslBundles and androidBundles pin the paths, the
--libc musl call and that nothing chmods unconditionally.
Thanks to xet7 !
It times out in every full Meteor run, before this round as well; the
profile at 3,000 cards and why it cannot simply run smaller are recorded
so the next step - profiling the board data read and the rollover at full
size - starts from there.
Thanks to xet7 !
It failed in every full Meteor run, before this round too, at Mocha's two
seconds. Timing the handler showed it refuses in 2 ms; the time was its
console.error of the refusal - the first error stack of the run, whose
printing loads the server bundle's source map. The test now captures that
log (and checks it was written) instead of printing it, and the group runs
in under half a second.
Thanks to xet7 !
- Tsonga: 618 values were the filler "Hi Xitsonga: mhaka", and 831 more
were Xitsonga words padded with a repeated "mhaka" wherever the
generator had no word ("engetela bodo swirho mhaka ku suka mhaka mhaka
nhlangano"). All are translated from the English, in the file's own
words (bodo, khadi, nxaxamelo, ndlela, xirho, nhlangano, ntlawa); the
planning-poker numbers and search symbols are literal again.
- Bosnian: the 748 values that were Serbian, transliterated in v12.44, are
ijekavian Bosnian now (premjestio, Neraspoređene, Historija, sedmica),
with the board, card and list words of the rest of the file instead of
the court-file metaphor they came with (spisi, predmet, postupak).
Several mistranslations went with it (Export card said convert, Edit WIP
limit said remove it).
- Aromanian "calendar" replaces the Italian "Calendario"; it is spelled as
in English, so it joins Aromanian's list of such words.
Low confidence, for a speaker to correct: Tsonga throughout, especially
colour names, migration (ku rhurhisa), backup, lock/unlock, "Xiluva" for
the letter greeting Dear, and Vuhlayiselo used for both storage and
archive; Bosnian ploča where the board title itself says Tabla.
tests/wrongLanguageCorrections20261014.test.cjs fails on any Tsonga filler
or padding, any ekavian form in the Bosnian file, and a lost placeholder
in either.
Thanks to xet7 !
v12.44 made the clear HTML layout the default for notification e-mail
(#4434), and with it every e-mail job carries `text`, its plain-text part.
The Sync notification plan validator accepted exactly seven job fields, so
it refused every plan: a rule or Sync run's notifications failed with
sync-notification-plan-invalid. groupKey and deliverAt, from non-default
delivery settings, were refused the same way before that.
The validator now takes what emailOutbox.enqueue takes - the seven fields
and the three optional ones, checked as enqueue checks them - so a plan
never holds a job its replay would refuse. tests/syncNotificationPlan
.test.cjs plans a job with all three and refuses bad ones, and pins that
the optional list is exactly enqueue's parameters; the full Meteor run
found it (Shared notification preparation, Stored Sync notification
delivery).
Thanks to xet7 !
The one image in the templates without alt was the attachment viewer's:
it is "Preview" until a picture opens, and then the file's name.
tests/iconOnlyControlLabels.test.cjs now also fails on any img without
alt. Part of #337.
Thanks to ndarilek and xet7 !
v12.44 shipped while they were in progress; the rest of the wrong-language
replacements and the two guard updates after it go in a new Upcoming
section, as the release rules say.
Thanks to xet7 !
Both failed before this round, on changes that were deliberate:
- The activity notification recovery list (#6768) has Retry all and Cancel
older as toolbar buttons, written `actions: [{ id, labelKey }]` like the
menu's panes; the guard now leaves toolbar actions out and still
requires every menu pane to render.
- Webhooks may reach internal hosts a site admin lists (b4d2604e61):
off Sandstorm, webhookFetch is fetchSafe except for those hosts, which
internalWebhookHosts.test.cjs pins; the guard now matches that branch
and still requires fetchSafe for everything else.
Thanks to xet7 !
The first half shipped in v12.44 with "Updated translations"
(b7346d2429): Spanish Save is Guardar, the Bosnian file is in Latin
script, Romanian's Italian words are Romanian, and single values in a
dozen locales. This finishes it:
- Latvian: 225 values were Lithuanian; they are Latvian now, in the
file's own words (dēlis, kartiņa, darbvieta, migrācija). Low confidence:
fuksīns, the Monte Carlo note's statistics wording, Teksta sagatave.
- The same wrong words under other keys: Macedonian Настройки in the All
Boards menu, the Persian settings typo there too, Cantonese 依戀 in the
attachment search operator, and Occitan "mot de Santa-Clara" for
password in six values (senhal).
tests/wrongLanguageCorrections20261014.test.cjs now also fails on any
Lithuanian left in the Latvian file and checks its placeholders. Left for
later, with a speaker: the Tsonga file's 596 "Hi Xitsonga: mhaka" filler
values, Aromanian "Calendario", and Bosnian ekavian forms.
Thanks to xet7 !
The full node suite run found them; each guard keeps its assertion and
says why it changed:
- boardPermission lists ActivityPub's public-only check (#4198): only a
public board is a fediverse actor, never an instance board.
- cardSectionCollapse reads the End date item to the next template, and
cardTitleEditClose lets the close anchor carry attributes: the #337
titles moved the icon past a fixed window and a bare-anchor pattern.
- oidcAutoRedirectLoop's harness provides FlowRouter, which the sign-in
page's onCreated now reads for SQRL's one-time sign-in (#2460).
- sharedDescription's own whole-tree check skips every _build* directory,
rspack's bundled copy of every file.
Thanks to xet7 !
Each was checked for which side was wrong:
- The LibreJS labels page sent Cache-Control: public; it is private now,
as fileCacheHeaders requires of every response.
- Closing a card marks it seen (#2111): that code moved below the #6686
popup handling, which labelsPopupStaleCardContext reads from the top of
onDestroyed; unreadChanges now finds it anywhere in onDestroyed.
- The shared header helper (#2805) moved below the swimlane's currentUser
helper, where largeBoardDataLoading pins it.
- The Story/Task kind, column set places and drawing strokes (#2012, #211,
#1178) get their rule e-mail dispositions: none is e-mail content.
- Guards updated for deliberate changes, each saying why: the Rules group
in Admin Panel Visibility (#2217), the rule under the team tabs in All
Boards (#802, the two around Workspaces still pinned where they are),
and a linked list's card clearing the title draft only after it was
added (#2809, every clear checked against the add before it).
Thanks to xet7 !
Each came with a group of its own, with an untranslated title and two more
rules in Admin Panel -> Settings -> Visibility. Both are optional features
this WeKan offers, which is what the Features group lists, so they are
there now, each still off by default and saved when switched.
tests/boardsVisibilitySettings.test.cjs counts the groups and their rules
again. The SQRL page and the Upcoming entries name the new place.
Thanks to xet7 !
Upcoming gets the work since the last update: the CodeQL alerts first, as
security fixes; SQRL under Sign-in; browser push, the activity feed and the
webhook activity picker under Notifications; the linked list, live shared
descriptions, drawing, unread changes, column sets and the team tabs in
their groups; a new Nextcloud and the fediverse group for CalDAV, WebDAV
and ActivityPub; bug fixes for the subtask index, the cardKinds startup
schema and the icon-only controls; and the translations. In short is
rewritten to cover them.
TODO Later: the "decided, waiting to be implemented" category is gone -
everything in it is built - and #337 keeps a category of its own as
ongoing work. #2460 leaves "needs a maintainer decision". #4790 is linked
where it was named bare.
tests/changelogFormat.test.cjs asked at least half of the TODO Later
categories to link issues. That was a proxy, and it stopped holding once
the decided issues were built: the rest are mostly status records no
single issue tracks. It now checks the thing itself - the triage
categories exist, and no category names an issue without linking it.
Thanks to xet7 !
The 15 strings of SQRL sign-in - the Admin Panel switch, the sign-in
button and its QR code, and Member Settings -> SQRL - in all 234 locales,
from each locale's own words for sign in, password, add, remove and
Member Settings. "Rescue code" is SQRL's printed recovery code and
"identity" the member's SQRL key, not an ID card. ja-HI is written in
hiragana, as its tag says.
Low confidence: chr, tlh, wal, ay, ace, rn, sm, st, csb, kw, fur, uz-AR,
iu, nah, gn, zgh, wo, sn, tn, rup, se, cv, kl, to, bua, bi, nap, la, ak,
bm, ff, mg, ny, ve, xh, tig, tpi, sah, vo, gv, dz, ee, ig, ha, lg, nd, qu,
mi, wa, yi, ks, fj, haw, ve-PP, wa-RR, ss, ts, om, lld, scn, bo, yo and
ku; "identity", "client" and "rescue code" were coined where the language
has no settled word.
Thanks to xet7 !
SQRL (https://www.grc.com/sqrl/sqrl.htm) signs in with a SQRL app or client
instead of a password. A site admin turns it on in Admin Panel -> Settings,
off by default; WeKan must be at an https:// ROOT_URL. A member adds their
identity once in Member Settings -> SQRL, by scanning the QR code or
opening their SQRL client; the sign-in page then offers Sign in with SQRL,
shows the sqrl:// link and its QR code, and signs in by itself once the
client has answered. With the client on the same computer (cps), the
client opens WeKan with a one-time token instead.
The protocol is GRC's SQRL Semantics: the client posts signed requests to
/sqrl, each verified with Ed25519 by Node.js's own crypto, no new
dependency for it. Every nut is random and used once, taken atomically;
each request must carry the server's previous reply unchanged; a request
from another address than the sign-in page's is refused unless the client
says noiptest; disable needs the identity's signature, and enable, remove
and rekeying need the rescue code's unlock key. An identity WeKan does not
know can neither make an account nor sign in. The page learns of success
by polling with a secret only it holds, and signing in uses that once.
The identity's public keys are a user field never published and not
writable from the client. Not offered on Sandstorm. sqrlonly and hardlock
are not supported. The QR code is qrcode-generator 1.4.4 (MIT, pinned).
models/lib/sqrl.js is the protocol with storage and signature check passed
in; tests/sqrl.test.cjs drives it with a software SQRL client using real
Ed25519 keys - adding an identity, the waiting page, cps, another network,
forged signatures, replayed nuts, altered replies, unknown identities,
another member's identity, disable/enable, rekey and remove - and pins the
server's single-use and visibility rules and the UI.
server/lib/tests/sqrl.tests.js runs in Meteor (2 passing): off by
default, then an identity added and signed in with once, through the real
/sqrl route and MongoDB. Documented in docs/Features/Login/SQRL.md. The
new strings are English placeholders until the translations that follow.
Thanks to xet7 !
Fixes#2460.
21 controls showed only an icon and had no title, so a screen reader
announced them as an unnamed link: the swimlane and list drag handles, the
close buttons of the inline forms, the received, start, due and end date
buttons of a card, the add-attachment and edit-custom-field buttons, the
notification drawer's menu and close, the avatar's edit button, the list
open and back carets on a phone, the import's add-member and History's
previous and next. Each now has a translated title, which
client/lib/accessibility.js also makes its aria-label; the existing
translations are used, so no new strings.
tests/iconOnlyControlLabels.test.cjs scans every template for a link or
button whose only content is an icon and fails on one without a name.
This is part of #337, which stays open for the rest.
Thanks to ndarilek and xet7 !
Everybody who has a card's description open now edits one shared Yjs
document, so two people typing at once both keep what they typed; before,
the later Save replaced the earlier one. The editor stays the Markdown
textarea: each change in it becomes the smallest Y.Text edit, and the
others' changes are written into it with the caret kept where it was.
Save writes card.description exactly as before.
The server keeps the document in a server-only collection and publishes it
only to people who may edit the card. Updates are merged with a write that
is conditional on the state merged into, so two editors at once cannot lose
each other's typing; each update is limited to 256 KB and the document to
1 MB, and rate limited. A description changed some other way (the API, a
rule, an import) starts the document again from the new text, under a new
generation, so an editor never merges the old document into the new one.
A document nobody has touched for a day is started again from the saved
description the next time it is opened.
yjs 13.6.33 (MIT) is pinned. tests/sharedDescription.test.cjs runs the
real Yjs: concurrent edits merge, a repeated state is harmless, the caret
follows remote edits, an emoji is never split; it pins the server's checks
and that nothing else touches the document.
Thanks to dalareo and xet7 !
Fixes#140.
A test run with METEOR_LOCAL_DIR writes rspack's output to
_build-local-test/, a bundled second copy of every source file. The guard
walked into it and reported a replacement the source had already fixed.
Thanks to xet7 !
Admin Panel -> Attachments has a WebDAV (Nextcloud Files) pane beside S3,
Azure and Google Cloud Storage: a folder address, a user and an app
password, with the same read and write switches, connection test, default
storage choice and Move Attachment target as the others. Files are stored
with PUT, read with GET and removed with DELETE; the folders are made with
MKCOL as needed, and the connection test is a PROPFIND of the folder.
The password is a secret field like the other backends' keys: never
published, and kept when the form is saved without it. The address is the
site admin's own setting, so a Nextcloud on WeKan's own network can be
used; redirects are refused so it cannot be pointed somewhere else.
With the CalDAV sync of the previous commit this completes #687. The
Nextcloud ExApp is built by .github/workflows/nextcloud.yml; publishing it
to the Nextcloud app store is the maintainer's own step.
Thanks to scriptPilot and xet7 !
Fixes#687.
Board Settings -> Date has Calendar sync. A board admin gives a calendar
collection's address, a user and an app password made for WeKan, and turns
it on. Each card with a due date is one event, <cardId>.ics, UID
<cardId>@wekan, an hour long, with the card's title and link; a card that
loses its due date or is archived has its event removed. Every 15 minutes
the events are read back, and a due date moved in the calendar comes back
to its card. Sync all now pushes every card at once.
The link and its password are in a server-only collection that is never
published, and only a board admin can read or change it. Requests go
through webhookFetch: the SSRF guard, so a calendar on WeKan's own network
is reached only when a site admin lists it under Internal destinations.
Text values are escaped as RFC 5545 requires - a semicolon included, which
the first version of the test did not catch.
Thanks to scriptPilot and xet7 !
Admin Panel -> Settings has a switch, off by default. With it on, every
public board is an ActivityPub actor, @board-<id> at this WeKan's address:
webfinger finds it, its actor document carries its public key, and its
outbox and followers collections can be read. New card activity on the
board is sent to its followers as signed Notes.
It is read-only. The inbox acts only on Follow and Undo Follow, and only
with a valid HTTP signature from the actor that sent it; nothing a
fediverse server sends changes a board. Private boards are never actors:
every board route goes through one helper that serves only a public,
unarchived board while the feature is on, and
tests/restBoardDomainsAccess.test.cjs now knows that helper. The keys are
in a server-only collection, and every outgoing request goes through the
SSRF guard.
Thanks to ASLLR and xet7 !
Fixes#4198.
The 16 strings of the next three commits: the Admin Panel switch that lets
public boards publish to the fediverse, Board Settings' calendar sync, and
the WebDAV attachment storage pane - in English, pending on Transifex, and
in all 234 other locales from each locale's own words for board, card,
due date, storage, folder and password.
Low confidence: tlh, chr, wal, ay, ace, rn, sm, st, kw, csb, uz-AR, iu, zgh,
nah, gn, wo, rup, se, sn, tn, kl, bua, bi, to, ti, nso, la, tig, ak, bm, ff,
gv, vo, sah, ve, tpi, dz, qu, ee, lg, ks, wa, ve-CC, bo, fj, haw, lld, om,
ss, ts, ve-PP and wa-RR; "sync", "folder", "server" and "app password" were
coined where the language has no settled word. "WebDAV (Nextcloud Files)"
names the storage in each language, so it is not left in English.
Thanks to xet7 !
Since a card can be a subtask of several parents (v12.10), its children are
found with { $or: [{ parentId }, { parentIds }] }. Only parentId had an
index, and a $or uses indexes only when every branch has one, so the query
was a collection scan - in the card remove hook for every removed card, in
card copy and in the Excel and PDF exporters. parentIds now has its own
index beside parentId's.
tests/cardQueryIndexes.test.cjs reads the branches of every $or in
models/lib/cardParents.js and requires an index for each field, so a new
branch cannot arrive without one.
Thanks to Raymond-Voogt and xet7 !
Fixes#6770.
#584 (bad HTML filtering regexp): tests/accountEmailHtml.test.cjs checked
that the e-mail layout escapes markup with /<img|<script|<b>/, which a
<SCRIPT> passes. It now lists every tag in the output, in any letter case,
and allows only the layout's own div, p, a and br; an upper- and mixed-case
input is tried as well. The renderer was already escaping them.
#585 (incomplete regular expression for hostnames): the same test made the
reset address into a RegExp; it compares the text instead.
Both are pinned as shapes in tests/securityAlerts20261012.test.cjs, which
found more of them: a case-sensitive <script> pattern in jsWebLabels.test.cjs,
a path spliced into generated code in singleFile.test.cjs (now passed through
the child's environment), and twelve test patterns with a host name's dot
unescaped. No regex literal in the tree leaves one unescaped now.
These are in tests, so there is no attempt at runtime to record in Admin
Panel -> Problems.
Thanks to GitHub CodeQL and xet7 !
It shipped in v12.43 (a6b2938e1c): Member Settings -> "Keep #label and
@member in new card titles". This pins the issue's own example: with the
switch on, "fix menu for #PROJECT by @member" stays whole; off, the default,
it still becomes "fix menu for by".
Thanks to biji and xet7 !
Fixes#1273.
The 39 strings of browser push notifications, the webhook activity picker,
the activity feed, linked whole lists, column sets, card drawing and the
organization/team tabs, in all 234 non-English locales, from each locale's
own board, card, list and member terms.
Low confidence: ace, ay, chr, csb, kw, rn, sm, st, tlh, wal, zgh, iu, nah,
gn, wo, tn, sn, se, rup, cv, bo, fj, haw, lld, om, ss, ts, ve-PP and wa-RR;
"column", "browser", "device" and "drawing" were coined where the language
has no settled word.
Wrong-language values corrected: Macedonian had the Russian "Уведомления"
and the Bulgarian "Сваляне" (now Известувања, Преземи); Catalan, its Spain
variant and Valencian had "Membres d'Afegeix" for Add members; Klingon had
s3-bucket in English. Catalan "No" is the Catalan word and is now accepted
as complete, as Aragonese "no" already was.
Thanks to xet7 !
The left menu of All Boards gets a tab for each organization and team
the member is in. A tab shows the boards shared with that group (through
the facet filter of #4791) and who is in it; the group's admins -
orgs.$.isAdmin, and now teams.$.isAdmin for teams - and site admins add
members by user name and remove them there. A group whose membership
comes from the sign-in provider (LDAP and other directory sync) is shown
read-only, even to a site admin: WeKan reads directory groups and never
writes them back. A member sees only their own groups; organization and
team memberships stay out of what a client may write.
models/lib/groupMembers.js decides access and builds the membership
changes; tests/groupMembers.test.cjs covers admins, site admins, members,
strangers, a directory-synced group, double membership and the server's
checks. The new strings are English placeholders until the next
translation pass.
Thanks to xet7 !
Fixes#802.
Card menu / Drawing opens a drawing surface: pick a colour and a pen
width and draw with a mouse, pen or finger; undo the last stroke or clear.
Saving keeps the strokes on the card (drawings), so the drawing opens
again for editing from the same popup, and uploads a PNG of it as a
card attachment through the normal upload - so it shows wherever a
picture does. "Download as SVG" builds the SVG from the strokes. The
drawing code is the dependency overlay's ("Red Strings", #3392): strokes
are SVG paths.
Only numbers, the allowed colours and pen widths ever reach the SVG -
a point or colour carrying text is dropped - so nothing typed becomes
markup, and the SVG is not uploaded as a file at all (uploads treat SVG
as dangerous content). models/lib/cardDrawing.js cleans strokes, builds
the SVG and replaces an edited drawing; tests/cardDrawing.test.cjs
covers paths, dots, edits, injected colours, widths and points, clamping
and the bound. A Meteor run started with the new card field. The new
strings are English placeholders until the next translation pass.
Thanks to aleroot and xet7 !
Fixes#1178.
A new board view, Column sets. A board keeps named column sets - say
Completion, Priority, Milestone - each with its own coloured columns, and
every card is in one column of each set, or "Not placed". Picking a set
shows its columns; dragging a card to another column changes only where
it is in that set - its list, swimlane, labels and members stay - and
each card shows, as coloured labels, its column in the other sets, as the
issue asked. Board admins make and delete sets and columns; moving cards
needs the right to edit a card. Clicking a title opens the card.
Registered like every view: the Board View menu (appended after Version
Control), the default order and VIEWS_ADDED_LATER, the user's boardView
values and the API description, and an instance feature group so the
Admin Panel can turn it off. models/lib/columnSets.js holds the logic;
tests/columnSets.test.cjs covers places, removed columns, the other
sets' badges, foreign columns, dotted keys and the bounds, and
boardViewMenu / boardViewSettings now expect the view last. A Meteor run
started with the new fields. Documented in
docs/Features/Board/Column-Sets.md. The new strings are English
placeholders until the next translation pass.
Thanks to kentonv and xet7 !
Fixes#211.
The add-list form gets "or link a list from another board": pick a board
you can see and one of its lists, and this board gets a list that shows
that list's cards as they are, live. They stay the source board's cards,
under its rules - opened, edited, moved by whoever may write the source
board - and a card typed into the mirror is added to the source list by
addCardToLinkedList, which checks the right to write in that list. In
the Swimlanes view the mirror shows its cards once, in the first
swimlane. The source cards come through linkedListCards, published only
to a subscriber who may see both boards, so the board publication is not
widened. A list cannot mirror its own board, an archived or foreign list,
or another mirror.
models/lib/linkedList.js decides; tests/linkedList.test.cjs covers the
selector, the swimlane rule, the refused links, both visibility checks
and the source board's write rule. A Meteor runtime run started with the
new list fields. The new strings are English placeholders until the next
translation pass.
Thanks to javen9881 and xet7 !
Fixes#2809.
Member Settings / Notification Settings / "Browser notifications on this
device": the browser asks for permission and gives a push subscription,
and from then on every notification the member gets in WeKan - the same
recipients, watching and muted kinds as the tray and e-mail - also pops
up from that browser even with WeKan closed; a click opens the card. Turn
it on per browser and device, up to ten per account.
web-push 3.6.7 (pinned) sends it with this WeKan's own VAPID key pair,
made once and kept in a server-only collection; only the public key
leaves the server. A subscription's endpoint is chosen by the browser and
POSTed to by the server, so it is accepted only over https and only at
the push services browsers use - an internal address or another site is
refused when saved and again before every send. Gone subscriptions are
forgotten. The service worker opens only paths of this WeKan. Not
offered on Sandstorm.
models/lib/webPush.js decides; tests/webPush.test.cjs covers the push
services, internal and look-alike hosts, foreign click targets and the
bound; server/lib/tests/webPush.tests.js runs in Meteor (passing): one
key pair, only the public key out, an internal endpoint refused. With
the webhook activity picker (c3321926ab) and the all-boards activity
feed (656af74b2c), this completes both issues. The new strings are
English placeholders until the next translation pass.
Thanks to saurabharch, hingerl and xet7 !
Fixes#2026,
Fixes#1377.
The Story and Task commit (0d77882c3b) declared cardKinds as
`type: [String]`, which the schema library rejects at startup ("Invalid
definition for cardKinds field: type may not be an array"), so WeKan
stopped. It is `type: Array` with a 'cardKinds.$' entry of the two kinds
now. Found by a Meteor runtime test run; tests/cardKinds.test.cjs now
pins the form and checks that no array shorthand type is left in the
schemas these changes touched.
Thanks to xet7 !
Member menu / "Activity on all my boards" (/activity) lists what
happened on every board the member sees in All Boards, newest first, a
page of 50 at a time: who, what - in the same sentence the notifications
use - on which card and board, and when, with the card linked. It never
carries a field's value, so an admin-only custom field cannot leak
through it; on a board where the member sees only cards assigned to
them, only those cards' activity is listed; and it is empty when the
Admin Panel hides activities or turns them off.
models/lib/activityFeed.js shapes and filters the entries;
tests/activityFeed.test.cjs covers the entry, an unseen board, the
assigned-only rule, the bound and the server's switches. Part of #2026
and #1377, with the webhook activity picker and browser push. The new
strings are English placeholders until the next translation pass.
Thanks to xet7 !
An outgoing webhook was sent every activity, or whatever its activities
list said with no way to change it in the interface. Its form now has
"Send this webhook:" with "Every activity" and a tick per kind of
activity - the same groups a member mutes notifications by (#572):
labels, members, assignees, comments, moves, dates, checklists,
attachments, custom field values, archive and created. Ticks are saved as
the act-<type> descriptions the activity hook matches webhooks by;
nothing ticked, or Every activity, stays "all", the default.
models/lib/webhookActivityPicker.js maps both ways;
tests/webhookActivityPicker.test.cjs checks that a picked group selects
exactly its activities through webhookActivitySelection, the form state,
and that a webhook never silently gets nothing. Part of #2026 and #1377,
with the activity feed and browser push. The new strings are English
placeholders until the next translation pass.
Thanks to xet7 !
Member Settings / "Mark cards changed since I last opened them", off by
default. With it on, a minicard gets a blue edge when anything of the
card changed after the member last opened it - its own fields (title,
description, dates, labels, members), its checklists and their items,
attachments and comments - until they open it. Turning it on records
when, so the cards a member never opened are not all marked at once, and
closing a card counts as having seen it, so their own edits made while
it was open do not mark it.
models/lib/unreadChanges.js takes the latest change across those
documents and compares it; tests/unreadChanges.test.cjs covers each kind
of change, seen since, off, older than the switch and the wiring. The
new strings are English placeholders until the next translation pass.
Thanks to Lacour20, martinpospech and xet7 !
Fixes#2111,
Fixes#1604.
The 16 strings added as English placeholders by the #1931, #1618, #2805,
#4791 and #2012 commits are translated in all 234 locales, each with that
locale's own words for board, card, list, swimlane and label, and its
usual agile terms for Story and Task (where that is the English word, a
still-correct variant such as "User-Story").
Low confidence, for a native speaker to check: ace, ak, am, as, ay, ba,
bi, bm, bo, bua, chr, co, csb, cy-GB, dz, ee, ff, fj, fo, fur, ga, gd, gn,
gv, ha, haw, hsb, ht, ig, iu, kl, km, km-KH, km_KH, kok, ks, kw, la, lg,
lld, mai, mg, mi, nah, nap, nd, nso, ny, oc, om, or_IN, ps, qu, rm, rn,
rup, rw, sah, sc, scn, sd, se, sm, sn, so, ss, st, szl, 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, yo, zgh.
Thanks to xet7 !
The commits since: story and task cards, an existing card from another
board as a subtask, a person responsible for each checklist item with
dated items in the calendar and Gantt, adding and dragging cards in the
Table view, one shared list header row, All Boards facets, and a board
admin adding only their own organizations and teams. #5141, #864, #1931,
#1618, #2805, #4791 and #2012 leave TODO Later.
Thanks to xet7 !
A card can be a Story or a Task: card menu / Card kind (or an ordinary
card again), with a book or a checked-box badge on the minicard. A custom
field can be limited to card kinds - "Only on these card kinds", none
ticked being every card as before. A kind's own fields are added to a
card when it gets that kind, and shown only on cards of it; a field of
no kind shows on every card. The one choke point every render uses,
customFieldsWD, leaves out fields of other kinds, so the card details and
minicard agree.
models/lib/cardKinds.js decides; tests/cardKinds.test.cjs covers the
fields a kind brings, not twice, other kinds' fields staying off, unknown
kinds, and the card menu, badge and the field's ticks. The new strings
are English placeholders until the next translation pass.
Thanks to nightwind27 and xet7 !
Fixes#2012.
A facet bar above All Boards shows the first characters of the board
names (one emoji or letter, any case), the organizations and teams the
boards are shared with, and the member's own board labels - each with
its count - and a click shows only that group, in the server-paged
sorted views and the drag-ordered one alike; again, or "Show all boards",
clears it. Board labels are the member's own (profile.boardLabels):
"apply label X to the boards whose name includes Y" files many at once,
and a label can be removed. Counts are over the boards the member sees
in All Boards - the same visibility selector as getAllBoardsPage, hidden
boards left out - never more.
models/lib/allBoardsFacets.js counts, builds each group's selector and
applies labels; tests/allBoardsFacets.test.cjs covers characters, an
inactive organization link, labels of boards no longer visible, escaped
and injected facet values, empty labels and the server's visibility. The
new strings are English placeholders until the next translation pass.
Thanks to xet7 !
Fixes#4791.
Board Settings / Swimlane gets "One shared list header row", off by
default. With it on, the Swimlanes view shows the list headers once - the
first swimlane's - and the other swimlanes no longer repeat them; that
row stays at the top while the board scrolls, through the existing
sticky list header rule of #3847, which the setting turns on, rather than
a second sticky mechanism (tests/stickyListHeadersSwimlanesView keeps
that so). The only new CSS rule is scoped to boards with the setting.
tests/sharedListHeaders.test.cjs checks the board field, the toggle, the
first-swimlane class, the reuse of the sticky rule and the scoping. The
new strings are English placeholders until the next translation pass.
Thanks to mikesutton and xet7 !
Fixes#2805.
Each checklist item gets a "who is responsible" button: the board's
active members to pick from, or nobody; the avatar then shows on the
item. A deny rule keeps the responsible person an active member of the
item's board. Checklist items with a due date (#4755) now appear in the
calendar and in the Frappe and dhtmlx Gantt views as "card: item", with
their own `ci:` ids: opening one opens its card, a calendar drag of one is
refused, and moving one in a Gantt moves only the item's own due date -
never the card's dates.
models/lib/checklistItemSchedule.js collects dated items (not of archived
or missing cards) and decides who may be responsible;
tests/checklistItemSchedule.test.cjs covers both and the wiring of the
three views. tests/checklistItemDueDate.test.cjs, which pinned #4755 as
"due dates only", now says the assignee is #1618's own field. The new
strings are English placeholders until the next translation pass.
Thanks to martinbruno90 and xet7 !
Fixes#1618.
The "add existing card as subtask" picker offered only cards of the same
board. It now has a board picker: the card's own board first, then the
other boards the member may write. Another board's cards come from the
server (crossBoardSubtaskCandidates), and picking one attaches it through
addCrossBoardSubtask, which checks on the server, with server data, that
the member may write both the parent's and the child's board, that the
child is an ordinary, not archived card, and that no loop forms through
any parent. The card keeps the parents it has. Same-board picks work as
before.
tests/crossBoardSubtasks.test.cjs runs the loop check across boards and
checks both write rights, the card types, the escaped search and the
popup. The new string is an English placeholder until the next
translation pass.
Thanks to TiibCD and xet7 !
Fixes#1931.
The 27 strings added as English placeholders by the #1297, #1293, #5847
and #4265 commits are translated in all 234 locales, each with that
locale's own words for board, card, members and its Member Settings menu
name; WeKan, Mattermost and https:// are kept as they are. Where a
language's word for passkey is the English one, the popup title is
"Your passkeys", so it is not left equal to the English.
Low confidence, for a native speaker to check: ace, ak, am, as, ay, ba,
bi, bm, bo, bua, chr, csb, cv, dz, ee, ff, fj, fo, gd, gn, gv, ha, haw,
hsb, ig, iu, kl, km_KH, kok, ks, kw, la, lg, lld, mai, mg, mi, nah, nap,
nd, nso, ny, om, qu, rn, rup, sah, sc, se, si, sm, sn, ss, st, szl, ti,
tig, tk_TM, tlh, tn, to, tpi, ts, tt, ug, uz-AR, ve, ve-CC, ve-PP, vo, wa,
wal, wo, wuu-Hans, xh, yi, zgh.
Thanks to xet7 !
The Table view only listed cards. Now a member who may write gets an add
row under the table - a title, a list and a swimlane - and can drag a row
onto another: the dragged card goes just before the target card, in the
target's list and swimlane, through the same card.move the board uses.
Adding goes through tableViewAddCard, which checks that list and swimlane
are on that board and not archived and that the member may write in that
list, as for any card added. Rows are not draggable without the right.
models/lib/tableViewDrop.js places the card; tests/tableViewDrop.test.cjs
covers between, first and same-list drops, a drop onto itself, the
server's checks and that only writers get the controls.
Thanks to guakamole and xet7 !
Fixes#864.
setBoardOrgs and setBoardTeams refused nothing but non-admins: a board
admin could link any organization or team of the instance to a board,
and with it give every one of its members the board. Now an organization
or team that is new to the board must be one the board admin belongs to;
a site administrator may add any. Links already on the board stay -
somebody else may have added them - and removing or switching one off is
always allowed. The pickers offer only the admin's own groups, and the
org and team publications, which gave non-admins nothing (so the pickers
were empty for them), now give a signed-in user their own groups' names.
models/lib/boardGroupLinks.js decides; tests/boardGroupLinks.test.cjs
covers own groups, existing links, removals, site admins, a foreign group
refused and not offered, and that both methods check before writing.
Thanks to chrisi51 and xet7 !
Fixes#5141.
v12.43 was cut before these commits, so they go into a new Upcoming above
it: passkey login and Log out everywhere, the login webhook, ntfy, the
clear HTML e-mail default, Mattermost direct messages, read-only Rules
for members, the rule e-mail member picker, the subtask landing swimlane,
hidden team boards, Share to WeKan on Android, Sandstorm card sharing and
the LibreJS labels page. #1953, #2217, #2290, #2435, #4434, #5148, #1040,
#1188, #1297, #1293, #5847 and #4265 leave TODO Later.
Thanks to xet7 !
A board a member reaches through one of their teams or organizations, or
their e-mail domain, could not be left: without a member entry quitBoard
refused it, and with one the board stayed in All Boards through the team.
Now leaving such a board puts it in the member's own hidden list
(profile.hiddenBoards): All Boards leaves it out, in the paged sorted
views and the drag-ordered one, and Member Settings / Hidden boards
brings it back with Restore. The team or organization membership is not
touched, and access stays the board's own. An All Boards tile of a board
the member is not on gets an eye-slash "leave" button; a member who also
has the board through a team is both removed and hidden. A board reached
only by membership leaves as before.
models/lib/boardInvites.js decides (planQuitBoard's hide,
reachesBoardOtherwise, hiddenBoardsAfter); tests/hiddenBoards.test.cjs
covers team, organization and domain reach, an inactive link, the bound,
that the team is not touched and the All Boards filters.
tests/boardInvites.test.cjs expects the plan's hide: false where nothing
else reaches the board. The new strings are English placeholders until
the next translation pass.
Thanks to LaureHades and xet7 !
Fixes#4265.
Member Settings gets Passkeys: add a passkey on this device under a name,
see the account's passkeys and remove one. The sign-in page gets "Sign in
with a passkey": the browser offers this WeKan's passkeys, no user name
needed. Verification is @simplewebauthn/server 14.0.3 (MIT, pinned)
against ROOT_URL's host and origin, so a passkey cannot sign in to another
site or through another address; each sign-in signs a fresh challenge
that is used once and expires in five minutes, and the signature counter
must move forward, so a copied passkey is refused. A disabled account
cannot sign in with one (the existing validateLoginAttempt hooks apply).
Passkeys are a top-level user field, never published and not writable
from the client; up to 20 per account. Not offered on Sandstorm.
Member Settings also gets Log out everywhere: after a confirmation every
login token of the account goes, so Meteor ends every session - other
browsers, phones, the desktop app - and this one. Nothing else changes.
models/lib/passkeys.js holds the relying party, base64url and the
browser conversions; client/lib/passkeys.js uses navigator.credentials
directly. tests/passkeys.test.cjs signs assertions with a software ES256
authenticator: a real signature verifies, and another key, another
origin, another challenge or a counter that went back are refused; it
also checks single-use challenges, the unpublished field and the pin.
server/lib/tests/passkeys.tests.js runs in Meteor (2 passing): the module
bundles, login options name no account, and registration needs a user.
Documented in docs/Features/Login/Passkeys.md. The new strings are
English placeholders until the next translation pass.
Thanks to xet7 !
Fixes#5847.
models/lib/sandstormShare.js (#1188) parses a page path against
http://grain.invalid, a reserved name that is never contacted, and cites
Sandstorm's documentation of startSharing.
Thanks to xet7 !
The web app manifest declares a share_target, so on a phone where WeKan
is installed as an app it appears in Android's Share menu. Sharing a web
page, a note or a message opens /share-target, "Add to WeKan": pick a
board and a list, and what was shared becomes a card there - the page's
title or the text's first line as the title, the text and the address in
its description, the address once - and the card opens. Only boards the
member may add cards to are offered, and the server checks the right to
write in that list, on that board, as for any card added.
models/lib/shareTarget.js turns a share into a card and holds the
manifest's declaration; tests/shareTarget.test.cjs covers what browsers,
notes and chat apps share, an empty share, javascript: addresses, the
manifest, the route and the server's checks. Documented in
docs/Platforms/FOSS/OS/Android.md. The four new strings are English
placeholders until the next translation pass.
Thanks to memeplex and xet7 !
Fixes#1293.
A saved outgoing webhook gets "Also send a Mattermost direct message to
the card's members and assignees". With it ticked, every message about a
card also goes to each of its members and assignees as the webhook's own
message with channel "@username" - Mattermost's direct-message channel -
so they hear about their cards without following the channel. The person
who made the change is not messaged, nobody twice, a name that is not a
valid Mattermost user name is skipped, and a card stops at 50. A board
webhook does this only for its own board's cards. The direct messages go
at once, best effort, through webhookFetch; the channel message keeps its
grouping and schedule. Two-way webhooks are left as they are.
models/lib/mattermostDirectMessages.js picks the people and builds the
messages; tests/mattermostDirectMessages.test.cjs covers them, the actor,
bad names, the bound, and that it is off unless ticked and sent through
the SSRF guard. docs/Features/Webhooks/Chat/Mattermost.md says what
Mattermost needs. The two new strings are English placeholders until the
next translation pass.
Thanks to xet7 !
Fixes#1297.
The 24 strings added as English placeholders by the #1953, #2217, #2290,
#2435 and #5148 commits are translated in all 234 locales, with each
locale's own words for rules, members, board, swimlane and subtasks;
@username and the ntfy.sh example addresses are kept as they are, and the
quoted label in the Rules option's description is that locale's own
translation of the board admin's tick.
The Acehnese "keep #label" strings from the earlier pass used Malay
"simpan"; they say "keubah", the file's own word, now
(tests/wrongLanguageLocales). The Maori, Northern Sotho, Somali and Tok
Pisin recovery suites count 50 recovery keys since the four bulk actions
of #6768.
Low confidence, for a native speaker to check: ace, ak, am, ay, ba, bi,
bm, bo, bua, chr, co, csb, cv, dz, ee, ff, fj, fo, fur, fy-NL, gd, gn, gv,
ha, haw, hsb, ig, iu, kl, kok, ks, kw, la, lb, lg, lld, mai, mg, mi, my,
nah, nap, nd, nso, ny, oc, om, or_IN, pap, ps, qu, rn, rup, rw, sah, sc,
scn, sd, se, sm, sn, so, ss, st, szl, tg, ti, tig, tk_TM, tlh, tn, to,
tpi, ts, tt, ug, uz-AR, ve, ve-CC, ve-PP, vo, wa, wa-RR, wal, wo,
wuu-Hans, xh, yi, yo, zgh.
Thanks to xet7 !
Inside a grain WeKan's own address reaches nobody, so the copied card link
was useless to anybody else. On Sandstorm, Copy card URL - in the card
menu and in the card's link popup - now sends the shell
{ startSharing: { pathname } }, which opens Sandstorm's Share Access
dialog; its sharing link opens the grain at that card. The shell accepts
only a path starting with "/", so only the card's path is sent. Outside
Sandstorm both buttons copy as before.
models/lib/sandstormShare.js builds the message;
tests/sandstormShare.test.cjs checks it, that nothing else reaches the
shell, and that both buttons share only inside a grain.
Thanks to AlexanderZobkov and xet7 !
Fixes#1188.
LibreJS runs a page's JavaScript only when it can tell the code is free.
A Meteor bundle is generated, minified and named by hash, so it cannot
carry per-file @license comments; LibreJS's other way is a JS Web Labels
table, linked with rel="jslicense". WeKan now serves /jslicense.html,
built from the client bundle it is actually serving (web.browser and
web.browser.legacy, under ROOT_URL's path), labelling each script with
the free licenses the bundle combines - Expat (MIT), Apache-2.0, Modified
BSD, FreeBSD and ISC, under LibreJS's own names and addresses - and with
WeKan's source at the running version. Every page links it from <head>,
whether or not the custom head is on.
models/lib/jsWebLabels.js builds the table and the link;
tests/jsWebLabels.test.cjs checks the scripts chosen from a manifest, the
table LibreJS reads, escaping and the wiring. releases/risk-baseline.json
allows the license addresses, and ntfy.sh, which the ntfy channel
(d873af70e8) names in a placeholder and comments.
Thanks to alexgleason and xet7 !
Fixes#1040.
Member Settings / Notification Settings gets "ntfy phone notifications":
a member pastes their ntfy topic's address, and an access token if the
topic needs one, and each notification they get in WeKan - the same
recipients, watching and muted activity kinds - also arrives in the ntfy
app, with the board and card as the title, what happened as the message
and a tap opening the card.
It uses ntfy's JSON publishing, so a title in any language needs no
header encoding, and goes through webhookFetch: the SSRF guard, Internal
destinations for a self-hosted ntfy, the Powerbox on Sandstorm. An
admin-only custom field value still reaches only board admins. One read
per activity finds the recipients who turned it on; sending is deferred
and best effort, so ntfy being down never touches e-mail or the tray.
The address and token are a top-level user field, saved and read only
through the member's own methods, not client-writable and never
published.
models/lib/ntfy.js parses topic addresses and builds the request;
tests/ntfy.test.cjs covers the request, bad addresses, tokens with header
injection, javascript: links, off, and that the setting is unpublished
and sent only through webhookFetch. Documented in
docs/Features/Notifications/ntfy.md. The new strings are English
placeholders until the next translation pass.
Thanks to xet7 !
Fixes#5148.
Notification e-mail now defaults to the clearly arranged layout of #5171 -
board first, board, actor and card in bold and linked, with a plain-text
alternative. One sentence (classic) is still one choice away at the
Admin Panel, board and member levels, and the tray keeps the classic line.
Account e-mails - reset password, verify e-mail, enroll account, and the
board and registration invitations - get an HTML part in the same style,
built from the same translated text, which is still sent as the text
part: no translation changes. Every piece of text is escaped first, the
link paragraph becomes a button, and only an http(s) address is linked. A
caller's own HTML and texts that are not WeKan's e-mail strings are left
as they were, and a failure to build the HTML leaves the text alone.
models/lib/accountEmailHtml.js renders it; tests/accountEmailHtml.test.cjs
lays out the real English texts and checks that markup in a name, a board
title or the site name stays text and only http(s) is linked.
notificationDelivery's default assertions now expect clear for e-mail,
and emailLocalizationPreparation's harness allows the new module, checks
the HTML part, and checks the absent-HTML case with literal text.
Thanks to Meeques and xet7 !
Fixes#4434.
The send-email action gets a member picker under To: typing filters the
board's active members by user name or full name, and a pick adds
@username to To. Addresses are still typed into To as before. When the
rule runs, each @username becomes that member's current email address,
so the rule - and whoever edits it - holds a user name, never somebody's
address, and a member who changes address is still reached. A @name of
somebody who is not an active member of the board, or has no address, is
dropped rather than sent as text, and nobody is mailed twice.
models/lib/ruleEmailMembers.js holds the parsing, the choices and the
expansion; tests/ruleEmailMembers.test.cjs covers them, non-members,
duplicates, an address with @ inside, and that the picker never reads an
email address. rulesHelper imports it, so the ruleEmailFailure harness,
which runs rulesHelper without require, is given it as it is given
ruleParts. The new strings are English placeholders until the next
translation pass.
Thanks to chenghongxi and xet7 !
Fixes#2435.
Admin Panel / Settings / Global Webhooks gets one address WeKan tells
when somebody logs in, when a new account is made, or both - nothing is
sent until an event is ticked. Each message is a JSON POST with the
event, the user name and the time; the email, the login's IP address,
the full name and the sign-in method are added only when ticked. A token
goes in X-Wekan-Token, as for board webhooks.
It is sent after the login, through webhookFetch - the SSRF guard,
Internal destinations and, on Sandstorm, the Powerbox - and a receiver
that is down only leaves a log line. The setting is read and saved
through site-admin methods and never published.
models/lib/accountWebhook.js cleans the setting and builds the message;
tests/accountWebhook.test.cjs checks the default carries name and time
only, each extra field only when ticked, the events separately, bad
addresses refused, and that no hash, services or user id is sent.
Documented in docs/Features/Webhooks/Login-and-Registration-Webhook.md.
The new strings are English placeholders until the next translation pass.
Thanks to KrunalDholakiya and xet7 !
Fixes#2290.
Admin Panel / Settings / Visibility / Rules gets "Boards may show their
Rules to members, read-only", off by default (Settings
rulesReadOnlyForMembers, site admin only). With it on, a board admin ticks
"Show these Rules to board members, read-only" on the board's Rules page
(rulesVisibleToMembers). Members of that board then get Rules in Board
Settings and see the rules list and each rule's details, with no control
that changes a rule; the methods that change rules keep their board-admin
checks, so nothing is granted. Without both ticks only board and site
admins see the Rules page, which until now drew its list for any member
who opened the address.
models/lib/rulesReadOnly.js decides 'edit', 'read' or 'none', and
client/lib/rulesAccess.js is the one place the Rules page, Board Settings
and rule details ask. tests/rulesReadOnly.test.cjs covers both switches,
each alone, a non-member, and that a reader's page has no edit control.
boardMenuOrder now leaves the reader's alternative branch out of the
admin's sequence and pins it alone; instanceFeatures counts the fourth
site-admin method; roleHelperRegressions follows the canAdmin check to
rulesAccess.js. The new strings are English placeholders until the next
translation pass.
Thanks to justinr1234 and xet7 !
Fixes#2217.
Beside the landing list, Board Settings / Subtasks now has a landing
swimlane, stored as subtasksDefaultSwimlaneId. As with the list, the main
board's choice wins on a shared deposit board, then the deposit board's
own; a swimlane that is archived, gone or on another board is never used.
With none picked, nothing changes: the swimlane with the parent card's
swimlane title, else the default one (#2732). Changing the deposit board
clears the choice, and a copied board does not keep its template's.
models/lib/subtaskLandingSwimlane.js decides; tests/subtaskLandingSwimlane
covers it, and tests/subtaskSwimlaneInheritance2732 now runs the chooser
for the title match instead of reading the inline lookup it replaced. The
two new strings are English placeholders until the next translation pass.
Thanks to BjoernLudwig and xet7 !
Fixes#1953.
v12.42 was cut on origin while Bots, board search reveal, label order,
avatar order, internal webhook destinations, the how-tos and the
notification recovery fixes were being recorded locally, and the merge
left their entries inside the v12.42 section although that release does
not contain those commits (tests/changelogEntriesBelongToTheirRelease
caught all eleven). The v12.42 section is restored exactly as released,
and those entries move to a new Upcoming, with the keep #label / @member
setting and the FreeBSD rc.d script. Both leave TODO Later.
Thanks to biji and xet7 !
releases/bsd/rc.d/wekan runs the bundle's own start-wekan.sh (bundled
Node.js and FerretDB on SQLite) under daemon(8) as an unprivileged
wekan_user, configured from rc.conf: wekan_dir, wekan_port,
wekan_root_url, wekan_data (WRITABLE_PATH), wekan_logfile and wekan_env.
It has no -r, because start-wekan.sh already restarts WeKan, and stop
signals the whole process group so FerretDB stops with node.
releases/bsd/build-bundle.sh puts it in the FreeBSD bundle as
bundle/rc.d/wekan, and bsd.yml fails a FreeBSD zip without it. The
FreeBSD doc has the install steps, the rc.conf table and an outline of a
ports Makefile to propose to FreeBSD.
tests/freebsdRcScript.test.cjs checks the rc.subr contract with sh -n,
that it never runs as root or restarts twice, and that the bundle ships it.
Thanks to xet7 !
Fixes#2662.
The two Member Settings strings added for #1273 were committed as English
placeholders; this fills them in all 234 locales, with each locale's own
words for label, member, card and title. "#" and "@" are kept as they are.
Low confidence, for a native speaker to check: ace, ak, am, ay, ba, bi,
bm, bo, bua, chr, csb, cv, dz, ee, ff, fj, fo, fur, gd, gn, gv, ha, haw,
ig, iu, kl, km-KH, kok, ks, kw, la, lg, lld, mai, mg, mi, nah, nap, nd,
nso, ny, om, pap, qu, rn, rup, rw, sah, sc, se, sm, so, ss, st, szl, ti,
tig, tk_TM, tlh, to, tpi, ts, tt, uz-AR, ve, ve-CC, ve-PP, vl-SS, vo, wa,
wa-RR, wal, wuu-Hans, yi, zgh.
Thanks to xet7 !
#6768: the recovery scan retried every pending activity notification each
round, and one whose delivery fails for good - its activity deleted or
changed since, a recipient who lost access, a plan with nothing or too much
to send - failed the same way every round, forever, so the list only grew.
Such an error is terminal now: after a ten-minute grace (an activity being
written is never touched) the scan cancels that notification through the
same cancellation an administrator uses, as the system, which compacts it
to a cancelled receipt. A pending notification older than
ACTIVITY_NOTIFICATION_MAX_AGE_DAYS (7) is cancelled as expired instead of
being delivered a week late. Network and other transient failures are still
retried. Admin Panel -> Problems gets "Retry all" and "Cancel older..." for
the whole list at once, site administrators only.
#6769: before each run synced-cron asks whether a job of that name is still
running - { name, finishedAt: { $exists: false } } - every minute for every
job on every instance, and only { intendedAt, name } and the TTL on
startedAt were indexed, so it scanned the job history. { name, finishedAt }
is created at startup with WeKan's ensureIndex.
The new strings are translated in every locale.
Thanks to Raymond-Voogt and xet7 !
Fixes#6768,
Fixes#6769.
Admin Panel -> Settings -> Global Webhooks -> Internal destinations: the
hosts on WeKan's own network - a Node-RED, a chat server, a Matrix hookshot
- that outgoing webhooks may reach, one per line, a name or an address,
with a port to allow only that port. Empty by default. Only site
administrators read and change it, through methods, and it is never
published, so a board member still can not point a webhook inward and the
list does not describe the network to anyone else. A listed host is called
directly without following redirects; every other address stays behind the
SSRF guard, and the REST routes that save a webhook accept a listed host as
well. The Node-RED how-to (#2017) says how.
The new strings are translated in every locale.
Thanks to xet7 !
matrix-hookshot's generic webhooks post a JSON body's top-level `text` into
a Matrix room, and WeKan's outgoing webhook already sends the activity as
a sentence in `text` - so a board's (or the instance's) outgoing webhook
pointed at a hookshot address is all it takes. WeKan needs no Matrix
account, token or bot.
Thanks to killua-eu and xet7 !
Fixes#1100.
A USB or serial RFID reader and a short Python script, as a WeKan user with
write access to the board: the scanned tag finds its card through an RFID
custom field (GET .../cardsByCustomField) and moves it to another list, or
comments on it, or creates a card for an unknown tag - and a Rule can do
the rest.
Thanks to xet7 !
Fixes#1126.
Bitbucket Cloud needs no provider of its own: an OAuth consumer with the
callback /_oauth/oidc, and the OAuth2 settings pointing at bitbucket.org's
login pages and api.bitbucket.org's /2.0/user, mapping uuid, nickname and
display_name. Checked against the code: an endpoint given as a full URL is
used as it is, and the client secret goes in the token request as Bitbucket
accepts it. Bitbucket's user record has no email, which the how-to says,
with what that means for OAUTH2_ALLOWED_EMAIL_DOMAINS.
Thanks to thuanpq and xet7 !
Fixes#1324.
The page described Rancher 1 with Rancher Active Proxy and the
wekanteam/wekan:meteor-1.4 image. It now describes Rancher v2: adding the
WeKan chart repository (wekan.github.io/charts), installing with ROOT_URL,
a release tag, a persistent volume for FerretDB's files under
WRITABLE_PATH and the cluster's ingress and certificate issuer, and
upgrading - in Rancher's own screens and with helm.
Thanks to Dreampie and xet7 !
Fixes#632.
The decision for #2171 was next AND previous; only next was there. Now
Shift+Enter in the search box steps to the previous card found, the arrows
beside "2 of 7" step either way for a mouse or a touch screen, and both
wrap round - back from the first is the last.
Thanks to rlach and xet7 !
A board's labels have an order now - a board admin moves a label up or down
when editing it (the same write, and permission, as editing a label) - and
Sort cards has "Label order": each card comes where its first label comes
in that order, cards without a label last, and cards that rank the same
keep the order they were dragged into. Like "Sort by votes" it is a display
order only: no card's manual position changes, so turning it off gives the
dragged order back.
The new string is translated in every locale.
Thanks to mnantel and xet7 !
Fixes#2040.
A board search found cards and listed them in the sidebar, but finding the
card on the board was still the reader's job; a browser's own find did not
help either, as the issue shows. Now a result opened from the sidebar is
also revealed on the board - scrolled into the middle of the view and
highlighted for a moment - and pressing Enter again in the search box steps
to the next card found, wrapping round, with "2 of 7" under the box, the
way finding on a page works. A list found is revealed the same way. The
highlight does not pulse for people who ask for reduced motion.
The new string is translated in every locale.
Thanks to rlach and xet7 !
Fixes#2171.
The minicard floated every avatar to the end of its row, which drew them
in reverse: the person added first, whom a team treats as the responsible
one, came last, while the opened card showed them first. The avatars are a
wrapping row aligned to the end now, in the order they were added - still
wrapping right-aligned as #5081 asked. Each person's initials avatar has a
colour of their own, the same everywhere and every time
(models/lib/avatarColor.js), every colour keeping the initials readable at
WCAG AA, and the avatar's tooltip names the person.
Thanks to HolgerGH and xet7 !
Fixes#2110.
Board Settings -> Bots, below Workflow view, is the Rules page's view for
bots: runners on your own server - an AI agent, a CI job, a script - that
the board hands cards to. It is laid out like kanbots in the board's own
theme, Font Awesome and drag and drop: a rail of bots, live runs and
activity; the board's lists as columns, each card with its run's state
(Queued, Running, Waiting for you, Ready to review, Done, Failed, Stopped),
the step it is on and the answers to a question it asked; and a drawer with
the card's thread, Hand to bot, Stop and a reply box. Board admins only,
like the rest of the Rules page, and never a board or public view.
A bot has no rights of its own, like Rules and workflows. WeKan only sends
to it: a bot is an outgoing webhook marked as a bot, subscribed to no board
activity and no reminders, and a dispatch goes through the ordinary webhook
queue - the SSRF guard, its signing token, retries, Sandstorm's Powerbox.
The runner works on the board through WeKan's REST API with its own user's
token and board role, and reports its run at one REST route that needs
write access to the board. Handing over is a board admin's choice, in the
view or with the new Rule action "Hand the card to bot"; an answer or a
reply is the admin's own comment, under their right to comment.
The GitHub- and Gitea-compatible APIs' routes are regular expression
literals now, and an instance board is Trello's "org" level.
The new strings are translated in every locale.
Thanks to xet7 !
v12.41 gave every board without a key one at startup by re-reading every
key of the instance for each board, and counting up from 2 again for every
board that shares a title: quadratic, and on 32,500 boards hours of slow
`{ key: { $exists: true } }` queries. Now the keys in use are read once,
the new keys are planned in memory with a counter per base
(models/lib/boardKey.js planBoardKeys, linear), and written in batches of
1000, each only where the board still has no key, so a key a board admin
set meanwhile is never replaced. A single new board reads only the keys
that could collide with its suggestion (an index range on the new `key`
index), not all of them. Planning 32,500 boards takes well under a second;
600 boards are written in under one on a running server.
Thanks to Raymond-Voogt and xet7 !
Fixes#6767.
Admin Panel / Settings / Visibility / Optional APIs ticks REST APIs in the
shape of Trello's, Jira's, GitHub's and Gitea's, so apps and scripts written
for them - Trellinator, Jira and GitHub clients - can use WeKan as their
backend, each described on the page with the address a client uses.
None has rights of its own. Each is a translator: a request in the other
tracker's shape becomes the matching request to WeKan's own REST API, made
with the caller's own WeKan token on the loopback address, and the answer
becomes the other tracker's JSON. Every WeKan permission therefore applies
unchanged - read-only and comment-only roles, assigned-only members, list
permissions - and an API that is not ticked answers 404. All of them sit
under /api, so WITH_API turns them off with the rest of the REST API.
Trello: members, boards, lists, cards, labels, comments and checklists; a
deleted card is archived. Jira: projects are boards (the board key), issues
are cards (DEV-7), statuses are lists and transitions move cards, with
simple JQL. GitHub and Gitea: repositories are boards and issues are cards,
open or closed as in Board View / Version Control, with labels, milestones,
comments and Link pagination.
The new strings are translated in every locale. A runtime test over real
HTTP shows a read-only member refused and a Trello-created card readable
through Jira and GitHub.
Thanks to xet7 !
Fixes#4930.
Import -> GitHub -> Import a whole GitHub organization makes every
repository of an organization or user its own board, opening in Board View
/ Version Control. Issues go through the ordinary importBoard, as the person
who started the job, with their comments, labels and files. Releases are
cards in a Releases list, their files attached only when chosen. Each
.github/workflows file becomes Rules. The wiki is read from its git
repository with isomorphic-git (1.42.4, pinned), every request through the
SSRF guard and only to github.com: a page is a card in the Wiki list, its
[[links]] point at and are attached to the linked pages' cards, and each
commit that changed it is a dated edit in the card's history, so Restore
brings back an older version. The job shows progress, can be cancelled,
pauses on a restart and stops at a rate limit; the token is kept in memory
only.
Version Control gains Releases and Wiki tabs: the wiki as GitHub shows it,
with the page list, _Sidebar and _Footer, links that stay in the tab, and
each page's history with Restore.
GitHub Actions workflows map to Rules: issue events to triggers, label,
close, reopen, comment, assign and create steps to actions, actions/stale to
an aging rule. Code - builds, tests, scripts - is never run: such a workflow
is a disabled rule with its YAML as the description. WeKan's Rules gain what
they lacked for it: a "commented on" trigger, an "add comment" action written
as the person whose change ran the rule and only where they may comment,
cron schedules in UTC, and a rule description shown on the rule. A disabled
rule no longer fires on a schedule or from its button, which it did.
The new strings are translated in every locale.
Thanks to xet7 !
v12.41 was already released when the Android bundles were committed, so
their entry was in the wrong section: it moves to a new Upcoming, and
v12.41's In short says again what was released. Upcoming records the
Version Control view, issue files, synced issue details, the public views
of a public board and the card QR code. #2015 leaves TODO Later.
Thanks to xet7 !
Version Control shows the board as a GitHub or Gitea issue tracker in the
board's own theme: cards are issues (open, or closed when archived, in a
Closed or Done list, or closed upstream), the board's labels are labels,
labels with a due date and the importers' milestone: labels are
milestones, comments are the conversation and attachments are the files.
The issue list takes GitHub's search words (is:open, label:, milestone:,
author:, assignee:@me, no:label, sort:). New issue, Close and Reopen write
through the ordinary collections, so the History tab - the board's
history table - shows them and Restore undoes them. Import links open the
GitHub, GitLab, Gitea and Forgejo importers, and each synced list has a
Sync now button.
The GitHub, GitLab, Gitea and Forgejo importers bring each issue's files:
user-attachments and user-images links, GitLab uploads, and Gitea and
Forgejo assets, downloaded through the import's SSRF guard and stored as
card attachments in Admin Panel / Attachments / Default Storage.
List sync has a new option, Comments, labels, state and files, off by
default: comments come from one paged repository-wide list (GitHub,
Gitea) or each issue's notes (GitLab), keyed by the tracker's ids, so a
second run writes only what changed, and nothing written in WeKan is
changed. Gitea issues are fetched without pull requests, and a rate
limited answer is waited out once or ends the run with the time to retry.
A public board's Show on / Default on Public Board settings are for the
public: visitors and non-members get them, read-only, and the board's own
members get the private side, so any view - Version Control, Email, Chat
and the rest - can be the read-only public view.
The card menu has a QR code of the card's link, drawn in the browser by
WeKan's own encoder, which the tests check module for module against
qrcode-svg.
The new strings are translated in every locale.
Thanks to xet7 !
Fixes#2015.
TODO Later's design questions block is now the decisions, each to be built
as decided: organization and team tabs, team-board hiding, read-only Rules
behind an Admin Panel option, log out everywhere and passkeys, linked lists,
search scrolling, avatars, label ordering, QR codes, subtasks across boards,
checklist item owners, Table view editing, column sets, Yjs co-editing,
drawing (PNG and SVG, editable), board grouping, card types, unread marks
for every card field, push notifications and an activity feed, Mattermost,
Matrix, ntfy and ActivityPub, a Trellinator-compatible API, an Android share
target, Nextcloud, FreeBSD rc.d, LibreJS and internal webhook destinations.
Gogs beside WeKan is closed as WONTFIX.
Thanks to xet7 !
Closes#6554.
Everything requested on 2026-10-13 is now built. What stays in TODO Later is
the one thing that cannot work, Jira list sync from a Sandstorm grain, so it
is not asked again.
Thanks to xet7 !
Each release's Linux bundle of the matching CPU is rebuilt inside Termux's
own image, on a runner of that CPU, with wekan/node-patches'
node-android-<cpu> of the same version, checked against its SHA256: npm
reinstalls the server packages run by that Node.js, so every native addon is
built for it, and sharp from source against Termux's libvips. Each addon's
architecture is checked, sharp must load, and the bundle must start and
answer HTTP 200 before it is published. A CPU whose Android Node.js is not
published yet is skipped and builds by itself once it is; the single-file
workflow, which already makes Android single files from these bundles, now
waits for them.
Thanks to xet7 !
CodeQL alert #583 (js/reflected-xss): tests/httpsServer.test.cjs's server
answered `served ${req.url} ...`, sending the request back as the body. It
now names the page by comparison, as plain text.
tests/noReflectedRequest.test.cjs keeps the shape out of every file: no
res.end/res.write/res.send writes a request's url, query, params, headers or
body into the response. Test code only, so nothing for Admin Panel / Problems
to record.
Thanks to GitHub CodeQL and xet7 !
The check counted every board-wide list whose position history recorded
the swimlane it was created in, so lists made since the automatic
un-binding stopped (#6515, released in v10.31) were counted too, and the
count kept growing. Restore would have bound them back to that swimlane,
hiding the cards they hold in other swimlanes - 490 cards in 14 lists on
the reporter's instance.
Now a list created after v10.31's release is never offered, being
board-wide by design, and a list with any card (archived ones too) in a
swimlane other than the one it would be bound to is never restored. A list
that really lost its binding, with cards only in its own swimlane, is still
restored. tests/listSwimlaneRestore.test.cjs and a runtime test on a real
database cover both rules and that case.
Thanks to Raymond-Voogt and xet7 !
Fixes#6766.
AppImage.yml wraps the musl bundles too, whose Node.js needs no glibc
libraries; tests/nodeRuntimeDependency.test.cjs still requires
node-runtime/libatomic.so.1 for every glibc bundle.
Thanks to xet7 !
Upcoming gains the musl bundles and AppImages, list sync through the
Sandstorm Powerbox, and Hong Kong Chinese and Flemish in their own
languages. TODO Later's Planned block now holds only the Android bundles,
which wait for wekan/node-patches, and records that Jira list sync cannot
work from a grain, so it is not asked again.
The Sandstorm REST API works with a grain API key and is documented in
docs/Platforms/FOSS/Container/Sandstorm/Powerbox-External-APIs.md since
commit 2cc0b59a51.
Thanks to xet7 !
Fixes#2167.
zh-HK had 90 values in Simplified Chinese, found with OpenCC; they are now
Traditional with Hong Kong usage. vl-SS (Flemish) held 158 values in about a
dozen other languages - Venda, Vietnamese, Venetian, Walloon, Papiamento and
more - now in Flemish, and its 225 values still in English are filled.
Values a test pins or an audit corrected are kept, and
tests/wrongLanguageLocales.test.cjs now covers both files.
Thanks to xet7 !
Each release's Linux bundle of x64 and arm64 is rebuilt inside Alpine on a
runner of that CPU: the musl Node.js of exactly the bundle's version,
checked against its SHA256, replaces the glibc one and its node-runtime
libraries; npm reinstalls the server packages so every native addon is
compiled against musl and sharp's musl package is the one installed; then
every addon's architecture is checked, sharp must load, and the bundle must
start and answer HTTP 200 before it is published. FerretDB and the MongoDB
Database Tools are static and stay. The AppImage workflow wraps them too, as
x86_64-musl and aarch64-musl, and starts each inside Alpine to check it.
Thanks to xet7 !
A grain's only way out is the bridge proxy, so list sync never reached a
tracker. A board member who may write saves the list's source and allows
the grain to reach that tracker's API root in the Powerbox; the grant
belongs to the tracker, found by the URL being fetched, so the periodic job
needs no signed-in user. GitLab's token goes as private_token, since the
bridge drops PRIVATE-TOKEN; GitHub, Gitea and Forgejo read public
repositories, since their token headers cannot pass; Jira, which signs in
only with an Authorization header, is refused with that reason. Off
Sandstorm list sync still goes through the SSRF guard, unchanged.
New strings translated in all languages.
Thanks to xet7 !
Upcoming gains them; TODO Later's Planned block now holds only what is
left - Android bundles, Sandstorm sync and import (whose credentials the
bridge proxy cannot carry) and Node.js musl bundles, with the questions
that decide them - and the translation block names the languages that need
a native speaker.
Thanks to xet7 !
The locale tag decides the language. Found on 2026-10-14 and rewritten:
ro and ro-RO (Italian, French and Spanish blocks), rup, lld, rm and ve-CC
(Italian; ve-CC also Zulu and Venda), gl and gl-ES (Portuguese), ace
(Malay/Indonesian), ve (Zulu), sk (Czech and Polish), hsb (Czech), vo
(Esperanto), bi (Tok Pisin), uz-AR (half Latin script, Uyghur and Kazakh
forms; now Uzbek in Arabic script). Machine-fill prefixes and notes are
gone from to ("Faka-Tonga:"), qu ("Kay willaymi:"), wal ("Wolayttatto:"),
hsb ("WeKan:"), an, fur and lg, each value now in its own language. cmn
and yue_CN no longer call a board a wooden plank; List actions means the
actions of a list in pa, pt, pt-BR, ru_RU, vi-VN, hu and ko; "No" is the
word in pa, lt and te-IN; et-EE's board is a tahvel.
Values a person or an earlier audit had already reviewed are kept: every
value a regression test pins, every audited correction, and every label
another string quotes. Poker cards show their numbers everywhere.
tests/wrongLanguageLocales.test.cjs keeps the old languages and prefixes
out and checks every placeholder against English; two older tests that
expected Venda in the Venetian file, and a prefix in Quechua, now expect
the language of the tag.
Low confidence, for a native speaker: to, qu, wal, lg, bi, vo, lld, rup,
ve, ace, uz-AR (its use of ې is for a speaker to settle), hsb and rm.
Thanks to xet7 !
tests/boardPermission.test.cjs lists every public-board check in the server
and models, so none silently leaves instance boards out: #4527's "everyone
signed in" is one, and it covers public AND instance boards. The Powerbox
webhook comment in imports/sandstorm/egress.mjs gave an example address,
which the release risk audit reports as an outbound URL; it now describes
the shape without one.
Thanks to xet7 !
A grain has no network but the bridge proxy, so an outgoing webhook now
leaves only through an API a board admin approved in the Powerbox for that
webhook's address: each saved webhook offers "Allow this grain to reach this
address". The grant belongs to the destination, not to a person, is found
by the URL being sent to, and is approved as the address's parent path so
the bridge delivers to the exact address. Every sender - immediate, grouped
and stored - goes through webhookFetch, which off Sandstorm, or for an
address no grant covers, is exactly the SSRF-guarded fetchSafe.
The REST API called with a Sandstorm API key acts as the key's user: in a
grain the bridge sets X-Sandstorm-User-Id itself. Only in a grain, only
after a WeKan token, only for an existing enabled account, and a key without
participate reads only.
New strings translated in all languages.
Thanks to xet7 !
Each is the platform's published Bun bundle ZIP, unchanged, inside that
bundle's own Bun: built on the platform itself (Alpine for musl), so the
runtime is exactly the one the bundle was tested with. The first start
extracts the bundle once into a per-version cache directory, without a
second copy of Bun, and every start runs index.mjs with the executable
itself as Bun. Like the Bun bundle it needs MONGO_URL and ROOT_URL.
The launcher reads the ZIP itself (stored, deflated, ZIP64, Unix modes,
symbolic links) and refuses any entry or link that would land outside the
cache directory. Before publishing, each file is checked: it names itself,
refuses to start without a database, extracts once and reuses that, and
loads every native addon of the bundle. Linux x64/arm64 (glibc and musl),
macOS and Windows; FreeBSD and Android have no runner to build on.
Thanks to xet7 !
Upcoming gains the features, the list archive fix, the CodeQL fix and the
Node-RED and Uberspace docs. TODO Later's decided block is done and removed;
the open requests are rewritten as the design questions that decide them,
and the locale files found in another language are listed.
Thanks to xet7 !
Node-RED: the http in flow, and that an internal address is refused by the
SSRF guard, so Node-RED needs a public name. Uberspace 7: the UberLab guide,
the update script, and email settings on the one environment line.
Thanks to yeurplc, jeyca and xet7 !
Fixes#2017,
Fixes#2009.
CodeQL alerts #581 and #582: htmlToText still removed script elements and
tags with regular expressions, looped through a helper CodeQL does not
follow. A one-pass scanner drops tags whole, script/style/head elements with
their content, and unfinished tags. The tree-wide guard no longer accepts a
fixed-point helper and also catches a whole element removed by pattern.
Thanks to GitHub CodeQL and xet7 !
Only template lists archived their cards, so a normal list's cards stayed
visible in the Lists, Table and Calendar views. A server hook on the list's
archived field now archives the cards of every list, whichever way it is
archived, and restore brings back exactly those - the rule #2292 gave
swimlanes. Cards archived with the list get the list's archivedAt, so a
browser clock that differs from the server's cannot keep them archived.
Thanks to Raymond-Voogt and xet7 !
Fixes#6765.
A board can let every signed-in user edit it, when Admin Panel / Settings /
Visibility allows it: a non-member then comments, edits and moves cards like
a Normal member, and their first edit makes them one. Board-wide settings
stay members-only.
Lists can allow or refuse editing and commenting per role (list menu, board
admins): built-in roles, custom roles and, on an open board, everyone signed
in. The server applies it to card, checklist, attachment and comment writes
and to moves into the list; the REST API honours a list's refusal on every
per-list card route. A direct client write of the setting is refused and
shown in Admin Panel / Problems.
Every board has a key, unique in the instance, suggested from its title and
set for existing boards at startup; card numbers show as KEY-7 and searching
for KEY-7 finds the card.
Shared labels in Admin Panel / Labels, for every board or an organization's
or team's boards; a board uses them beside its own, and a rename or colour
change reaches every board that uses it.
New strings translated in all languages. Low confidence: chr, tlh, wal, ay,
ace, nah, gn, iu, zgh, rup, wo, se, ff, tig, vo, bm, kl, bua, nap, mai, to,
ks, qu, bo, fj, haw, om, ss, ts, ve-PP, lld, scn.
Thanks to Meeques, justinr1234, tiFabGrinch, ejlkemper and xet7 !
Fixes#4575,
Fixes#4527,
Fixes#2449,
Fixes#2450,
Fixes#2761,
Fixes#4698.
#916 (decided 2026-10-14): HTTPS_PORT with HTTPS_CERT_FILE and
HTTPS_KEY_FILE (a path or the PEM itself) makes WeKan serve HTTPS; without
them nothing changes, and a proxy stays the recommendation for
production. Meteor's HTTP server - pages, DDP, websockets - is not
replaced: a TLS listener hands each decrypted connection to it, so
everything works the same over HTTPS. A renewed certificate is loaded
within a minute without a restart (a broken one keeps the old).
HTTPS_REDIRECT=true redirects plain HTTP to ROOT_URL when that is https,
else the same host on the HTTPS port, keeping only the path. The snap
gets https-port, https-cert-file, https-key-file, https-bind-ip and
https-redirect.
tests/httpsServer.test.cjs makes a real certificate with openssl, serves
a page and a websocket upgrade through the bridge, swaps the certificate
without a restart, and refuses incomplete settings, an untrusted
certificate, an injected host and a scheme-relative redirect.
Thanks to 68040 and xet7 !
Fixes#916.
Upcoming gets the three features and the bug fixes; TODO Later's
decided-and-waiting list drops #3022, #4178 and #1455, which are done.
Thanks to xet7 !
Found while mapping every role check for custom board roles:
- canImportIntoBoard tested the role helpers as references
(`!user.isWorker`, always false), so Import was offered to nobody; it
asks the write capability now, as the server does.
- the rules list's canAdmin used `user.isBoardAdmin` without calling it,
so every member saw the rule controls.
- the role popup's isLastAdmin compared activeAdmins() - a list - with 1,
so the last admin was never stopped from giving the role away.
- the members widget's isWorker called Boards.hasWorker, which is not a
collection function.
- OIDC's DEFAULT_BOARD_ID used Array#contains, which does not exist, so
the configured role flags threw; isCommentOnly works next to the
documented isCommentsOnly spelling.
tests/roleHelperRegressions.test.cjs pins each, and fails for any role
helper tested as a bare reference anywhere in client/, models/ and
packages/, and for a list compared with a number.
Thanks to xet7 !
#3022 (decided 2026-10-14): a site admin builds named roles in Admin
Panel / People / Roles from the capability matrix - sees all cards,
comments, edits, moves cards, invites - and a board admin gives one to a
member in the same popup as the built-in roles. Board admin stays
built-in. Every permission check asks boardRoleCapabilities, which
answers a custom role from the role; the member's built-in flags carry
the strongest built-in role that grants nothing more (fallbackRoleFor,
checked for every combination), so code reading a flag never grants
more, and a deleted role leaves exactly that. Where a role is asked by
name (hasWorker, isCommentOnly, ...) a custom role reads as the built-in
role that does what it does; commenting is asked as a capability
everywhere. Invites, REST whole-board reads, imports (a foreign custom
role is dropped) and member mapping (all eight flags now, not four)
follow it. The six new strings are translated in all 234 locales.
#4178 (decided 2026-10-14): a team or organization that gives board
memberships takes them away again when the user leaves it - only those:
each records where it came from (grantedBy), a direct membership is
never touched, a board admin is kept, and a board admin's own add or
removal is final. Teams through user edits, LDAP, OIDC and the board
sidebar; organizations where they propagate members.
Also: the Roles Status table gets a Move cards column; the comment form,
reactions and chat ask the comment capability (a No comments member was
offered reactions the server refused).
Tests: customBoardRoles and groupBoardMembership node suites, and the
runtime suites (9 passing) against a real database.
Thanks to xet7 and mfnalex !
Fixes#3022,
Fixes#4178.
HTTP_PROXY, HTTPS_PROXY and NO_PROXY now work for every outgoing request
that goes through the SSRF guard: webhooks, imports, avatar downloads,
list sync. The destination is still resolved once and checked against
the blocked ranges first, and the proxy is handed that checked address,
never the name: an http:// request goes to the proxy in absolute form on
the pinned address with the real Host, an https:// one through a CONNECT
tunnel to the pinned address with TLS still verified against the real
name (and WEBHOOK_TLS_CA_CERT). A name resolving to an internal address
is refused before the proxy is asked anything. Proxy credentials in the
URL become Proxy-Authorization. The snap gets http-proxy, https-proxy and
no-proxy.
tests/outboundProxy.test.cjs runs a real proxy on loopback: the pinned
address, the Host header, the credentials, the CONNECT authority, and
that a refused destination never reaches the proxy.
Thanks to gbarre and xet7 !
Fixes#1455.
#570, #571 (incomplete multi-character sanitization): email to card
turned an HTML-only email into text with one pass of each removal, so a
script element spliced together by removing an inner one survived and its
contents reached the card ("<scr<script></script>ipt>alert(1)</script>").
Both removals now run to a fixed point.
#574 (regular expression injection): the single file's wekan.env writer
spliced the setting name into a RegExp. A setting must be an environment
variable name, is compared literally, and its value must be one line. The
kept-versions reader built a half-escaped RegExp from the file extension;
it now reads wekan-<version><exe> literally.
#572, #573 (incomplete string escaping): two runtime tests escaped only
some metacharacters; they compare literally now. #575-#580 (URL substring
and hostname patterns): translation tests matched example hostnames with
unescaped dots or substring checks; the dots are escaped and the hosts
bounded.
tests/tagStrippingFixedPoint.test.cjs missed the <[^>]+> spelling; it now
catches both, accepts a fixed-point helper only when it really loops, and
records the search-index text of server/lib/documentGif.js as never
rendered. tests/emailToCard.test.cjs fails on the old htmlToText;
tests/singleFile.test.cjs refuses regex and newline input in setting
names and values, and reads only real version files.
Nothing here is an attempt a user can make at runtime that the server
refuses, so there is no Admin Panel / Problems key to add.
Thanks to GitHub CodeQL and xet7 !
Shared labels (#2761, #4698), board keys for card numbers (#2449,
#2450), membership removal on leaving a team or organization (#4178),
webhooks through an HTTP proxy (#1455), custom roles (#3022), editing by
every signed-in user (#4575), per-list permissions (#4527) and optional
HTTPS in WeKan's own server (#916) are decided; each moves from the open
requests to a list of decisions waiting to be implemented.
Thanks to xet7 !
node-patches now builds OpenBSD's Node.js on 7.8, the first release whose
base Clang (19.1.7) builds Node 26 - 7.7's Clang 16 has no
std::atomic_ref. The bundle is built and started where that binary runs.
The BSD page says OpenBSD 7.8 or newer is needed, and that NetBSD's
bundled OpenSSL gets FreeBSD's configuration; tests/bsdBundles.test.cjs
fails for an OpenBSD target older than 7.8.
Thanks to xet7 !
Remove what is no longer open: #3318 (closed as not planned), #6692 and
#6509 (closed as completed), and email to card and the Node.js single
files from Planned (shipped in v12.40). The September translation-repair
checkpoints are cut to what is still open.
Every one of the 62 open issues was read with its comments and checked
against the code on 2026-10-14. None other than #2141 is fully done; each
is now listed under its topic with what exists and what is missing.
Record the #2141 closure in Upcoming.
Thanks to xet7 !
The issue asked for Trello's Send Emails choice per member: Never,
Periodically (about once an hour) or Instantly. All three already exist:
email off is the member's Notification Settings override (bde66a5c7d,
2026-09-10), and the member's delivery schedule is immediate, every 5
minutes to 24 hours, or daily, with quiet hours (ab785acd1e, 2026-10-10).
A member's choice wins over the board's and the Admin Panel's.
tests/notificationFrequency2141.test.cjs pins each option at member
level, that an unknown member value is ignored rather than trusted, and
that the switch and the schedule picker are in the member's settings.
Thanks to saurabharch and xet7 !
Fixes#2141.
Preserve Wrike status behavior, directory-sync identifiers and subtask permission distinctions. Add catalog and browser coverage. Technical prose is lower confidence and needs fluent review.
Thanks to xet7 !
Preserve user mapping choices, missing-column handling and single-board versus multi-board ZIP behavior. Add catalog and browser coverage. Technical prose needs fluent review.
Thanks to xet7 !
Distinguish link directions and permission limits, translate announcement controls, and repair the custom-fields label. Add regression assertions and browser coverage. Direct technical wording needs fluent review.
Thanks to xet7 !
Add positive and negative regression coverage for attachment visibility, limits, self-links and detachment. Direct technical wording needs fluent review.
Thanks to xet7 !
Restore initial date setting, Gregorian ISO-week details and destination versus card-derived board actions. Add positive and negative catalog assertions and browser coverage. Direct wording needs fluent review.
Thanks to xet7 !
Translate Todoist and Planner instructions and distinguish recurring cards from resets and move reasons from descriptions. Add catalog and browser regression coverage. Direct technical translations still need fluent review.
Thanks to xet7 !
Fill 27 English strings, preserving user choices, ZIP exceptions and workbook layout. Technical prose needs fluent review; extend regressions.
Thanks to xet7 !
Retain export prerequisites, field mapping and omission warnings. Technical prose is lower confidence and needs fluent review; extend regression coverage.
Thanks to xet7 !
Fill 26 English placeholders with direction, visibility and permission conditions intact. Add regression coverage; fluent review remains needed.
Thanks to xet7 !
Fill 41 English placeholders and add attachment and credential regressions. Technical storage wording is lower confidence and needs fluent review.
Thanks to xet7 !
Use assignee event wording and retain subjects in rule fragments. Omit the extra copula after Irish temporal clauses, with locale-boundary and assembled-rule regressions.
Thanks to xet7 !
Correct report titles and replace shorthand with methodology, uncertainty, history requirements and date fallbacks. Add regression coverage.
Thanks to xet7 !
Distinguish recurrence from reset, restore optional move reasons and permanent anonymization wording, and translate stale Todoist and Planner guides.
Thanks to xet7 !
Fill 67 English placeholders, preserving link directions, permission checks and storage key distinctions. Extend translation and browser regression coverage.
Thanks to xet7 !
Restore set-or-change date triggers, calendar labels and time attribution limits. Cover recurrence and time report wording with regression checks.
Thanks to xet7 !
Replace unrelated export text in account anonymization and expand five report methodology notes. Preserve destructive consequences and analytical limits with regression coverage.
Thanks to xet7 !
Translate old English Todoist and Planner guides, restore Jira pagination and Excel worksheet behavior, and correct CSV delimiter terminology. Extend semantic and browser coverage.
Thanks to xet7 !
Translate 29 entries preserving permanent deletion, unchanged records and rollback safeguards. Add a full-catalog placeholder gate and extend recovery browser coverage.
Thanks to xet7 !
Translate 25 entries for Wrike, LDAP, OAuth, subtask permissions and Blockly fragments. Preserve identifiers and placeholders and extend regression coverage.
Thanks to xet7 !
Translate 35 messages covering member and column mapping, bulk transfer layouts, Trello export and Sandstorm access. Preserve technical markers and add semantic and browser coverage.
Thanks to xet7 !
Translate twelve project import guides with explicit omission warnings, English column requirements and nested-task markers. Extend semantic and browser coverage.
Thanks to xet7 !
Translate ten import guides while preserving format names, data mappings, completion dates and first-board restrictions. Extend semantic and browser coverage.
Thanks to xet7 !
Fill 67 English placeholders including field-link permissions and directions, announcements, storage settings and rule connectors. Extend catalog and browser regression coverage.
Thanks to xet7 !
Distinguish destination linking from creating a board from the current card, explain version lookup failure and configured bounce thresholds, and extend regression coverage.
Thanks to xet7 !
Restore Jira pagination and Excel worksheet mapping explanations. Use Haitian preview terminology and clarify external tracking. Add semantic and browser coverage.
Thanks to xet7 !
Replace incomplete shorthand with translated methodology, missing-history rules, forecast limits and date fallbacks. Clarify movement reasons and never-synced state; add semantic and browser coverage.
Thanks to xet7 !
Distinguish groups from enabling limits, clarify swimlane selection and describe numeric value sums instead of field counts. Add catalog and browser regression coverage.
Thanks to xet7 !
Replace incorrectly duplicated export guidance with the actual permanent identity changes, avatar removal, disabled login and preserved history. Add semantic and confirmation-popup regression coverage.
Thanks to xet7 !
Correct eight labels for recurring cards, automatic unchecking, checking all items and known accounts. Add semantic and popup regression coverage.
Thanks to xet7 !
Repair nine labels and verify the production parser accepts attachment presence and absence queries while rejecting invalid predicates. Extend browser search coverage.
Thanks to xet7 !
Correct ten upload and control labels. Preserve map administrator permissions and point archive restoration to All Boards; extend semantic and map-upload browser coverage.
Thanks to xet7 !
Correct 30 entries across card controls, activity text and settings. Restore matching and template-copy wording, preserve attachment retention, and extend subtask browser assertions.
Thanks to xet7 !
Correct 81 entries: attachment terminology throughout the catalog, checkbox actions, date initialization triggers and assignee grammar. Preserve placeholders and deletion distinctions, and add rule-builder and attachment-settings UI coverage.
Thanks to xet7 !
Translate eight current placeholders and two stale-English guides. Preserve Scrum recovery permissions and data retention, add the current catalog gate and extend six browser cases. Semantic review remains separate.
Thanks to xet7 !
Translate 48 entries with retained-data, permanent-deletion and replay restrictions intact. Extend catalog and browser coverage for planning sync and interrupted operations.
Thanks to xet7 !
Translate 45 administration, workflow, Blockly and Scrum entries. Preserve permissions, completion groups, source matching and placeholders; cover subtask editing and denied access in the browser suite.
Thanks to xet7 !
Translate 30 mixed-language controls and messages, preserving deletion prerequisites, Cc syntax, window-action distinctions, ticket states and history actions. Add regression and browser coverage.
Thanks to xet7 !
Translate 25 mixed-language entries, preserving API aggregation and configuration, recovery actions and automatic continuation. Add regression and office-report browser coverage.
Thanks to xet7 !
Translate 37 mixed-language entries, preserving dependency direction, completion polarity, import formats and unmatched counts. Keep the exact shared Latin word Color and add regression and browser coverage.
Thanks to xet7 !
Translate 32 mixed-language entries, preserving query placeholders while explaining OR/AND, negation, sorting, date ranges and archive defaults. Localize user, absence and card-number examples with regression and browser coverage.
Thanks to xet7 !
Translate 40 mixed-language search entries, preserve validation constraints and placeholders, and use a localized list example. Verify parser registration and shared selector wording; add overdue-search browser coverage.
Thanks to xet7 !
Translate 48 mixed-language entries, retaining search access boundaries and placeholders. Preserve shorthand aliases and verify distinct localized operators are registered in the parser, with browser coverage for title filtering.
Thanks to xet7 !
Translate 50 action fragments and descriptions with matching movement and checklist wording. Recognize the exact shared Latin imperative Remove without exempting longer English prose. Add regression and browser coverage.
Thanks to xet7 !
Translate 33 prefixed trigger fragments and repair three unprefixed assignee/date entries. Omit the redundant linking verb for Latin finite predicates, preserving other locales and testing assembled and saved descriptions.
Thanks to xet7 !
Translate 25 mixed-language organization, membership, date and color labels. Preserve tenant configuration requirements and the limits of delegated administration, with regression and browser coverage.
Thanks to xet7 !
Correct 40 prefixed strings, restore literal size units and invitation variables, preserve separate server safety caps and extend attachment-limit browser coverage.
Thanks to xet7 !
Correct 30 prefixed strings, distinguish upload states and logo fields, preserve WIP recovery choices, and add warning browser coverage.
Thanks to xet7 !
Correct 30 prefixed strings, retain Sandstorm access revocation warning and all-card removal notification, and distinguish self-membership from assignment in shortcut coverage.
Thanks to xet7 !
Correct 30 prefixed strings, preserve public-read/member-edit and assigned-card boundaries, and extend archive-tab browser coverage to Latin.
Thanks to xet7 !
Correct 25 prefixed strings, preserve current-user fallback, four-digit years and the last-administrator restriction, and extend keyboard-shortcut browser coverage.
Thanks to xet7 !
Correct 40 prefixed strings, preserve ZIP limits, credential requirements and destructive cancellation scope, and add file and credential-label browser coverage.
Thanks to xet7 !
Correct 30 prefixed messages, update stale Jira pagination and Excel sheet guidance, preserve literal filter examples, and extend Latin import browser coverage to nine more formats.
Thanks to xet7 !
Correct 30 prefixed strings, retain distinct due periods and missing-value filters, and distinguish members, assignees and creators. Add date-filter browser coverage.
Thanks to xet7 !
Correct 50 prefixed strings, restore date-format tokens and enrollment email paragraphs, and preserve field deletion and global administrator warnings. Add form browser coverage.
Thanks to xet7 !
Translate 32 prefixed strings, including all 25 color names, and update the stale archive restoration location. Add color distinctions and skip-link coverage. Less common shade descriptions remain lower-confidence pending linguistic review.
Thanks to xet7 !
Correct 35 prefixed strings, preserve font size distinctions, idle-day fading levels and movement directions, and extend font preview browser coverage.
Thanks to xet7 !
Correct 45 prefixed strings, restore numeric poker choices, distinguish account anonymization from import, and cover positive and negative voting labels.
Thanks to xet7 !
Correct 44 prefixed mixed-language strings, preserve visibility and assignee scope, and add semantic checks and calendar menu browser coverage.
Thanks to xet7 !
Correct 55 mixed-language strings and test personal/shared scope, toggle direction, data-loss warnings and permanent attachment deletion. Add a browser toggle regression.
Thanks to xet7 !
Correct 60 mixed-language strings, retain the valid shared Latin word Color, and test argument order, opposite states and non-destructive Home removal. Add workspace navigation browser coverage.
Thanks to xet7 !
Replace 65 prefixed strings with Latin, preserving positional title arguments, move direction, checklist state and comment permissions. Add semantic regression checks and card-history browser coverage.
Thanks to xet7 !
Fill 51 pending strings and test matching precedence, retained changes, permanent deletion and recovery restrictions. Register Latin recovery browser scenarios.
Thanks to xet7 !
Fill 35 pending translations and repair preview and history labels. Preserve import restrictions, configuration names and placeholders, and extend regression and browser coverage.
Thanks to xet7 !
Fill 35 pending labels and instructions while preserving member mapping, ZIP boundaries, field placeholders and Trello round-trip behavior. Extend semantic and browser checks.
Thanks to xet7 !
Preserve attachment exclusions, source menu and column names, hierarchy markers, archive state and import format choices. Extend semantic and browser instruction checks.
Thanks to xet7 !
Translate twelve pending import guides while preserving source menus, format markers, hierarchy mappings and limits. Replace mixed English in the Trello guide and add thirteen browser cases plus semantic checks.
Thanks to xet7 !
Fill 34 pending English strings and repair two malformed card-link labels. Cover link direction, permissions and field matching in semantic tests and add Latin browser cases.
Thanks to xet7 !
Fill 40 English placeholders for sorting, actors, recipients, self-selection, counts and Somali Blockly controls. Preserve existing translations and extend alias and token checks.
Thanks to xet7 !
Fill 126 English file values, covering 140 locale entries through the Khmer alias. Keep control aliases and placeholders consistent and extend regression and browser coverage.
Thanks to xet7 !
Fill 40 English placeholders in Bengali, Nepali, Hungarian, Icelandic, Albanian and Tagalog. Use numeric separators where inflection cannot fit the count template, and correct the Tagalog sublist endpoint. Extend semantic, token and Blockly coverage.
Thanks to xet7 !
Fill 52 remaining English placeholders while preserving correct shared words and existing Chinese translations. Add token and meaning checks and extend S3 and Blockly browser coverage.
Thanks to xet7 !
Clarify message content, labels, recipient maximum and SES sender direction. Remove mixed English in email help and add semantic and settings UI regressions.
Thanks to xet7 !
Distinguish estimates from size and translate the sprint progress term. Extend backlog UI and semantic coverage for Scrum and recovery boundaries.
Thanks to xet7 !
Translate inverse tangent, use angular-degree terminology and repair vertical movement shortcuts. Extend semantic checks while preserving valid loop, list and text instructions.
Thanks to xet7 !
Replace abbreviated chart notes with methodology, missing-data rules and uncertainty caveats. Clarify time corrections, synchronization and credentials, with semantic regressions and seven browser cases. Statistical terminology remains subject to native-speaker review.
Thanks to xet7 !
Distinguish archived records from saved data and orphaned cards from ownership. Translate remaining S3 prose and extend semantic and browser coverage.
Thanks to xet7 !
Review source keys 2480–2599. Replace the unrelated export description with the account effects and irreversible-action warning. Clarify whole-instance backup scope and add semantic and browser checks.
Thanks to xet7 !
Review source keys 2135–2349. Correct recovery severity, template-copy labels, translation counts and time until due. Add semantic and browser regression coverage.
Thanks to xet7 !
Review source keys 1990–2134. Express positive and negative integer signs explicitly and identify the maximum allowed background image size. Preserve query syntax and add semantic and browser checks.
Thanks to xet7 !
Replace the blanket short-letter exemption with exact key/value symbols. Translate 20 Southern Sotho rule, Blockly and personal-filter strings. Add inventory and browser regression coverage while preserving existing Chinese values.
Thanks to xet7 !
Review source keys 1417–1589. Restore date-set triggers, auto-detection, rule states, assignee conditions and removal of labels. Translate short English fragments and add semantic and browser checks.
Thanks to xet7 !
Review source keys 1280–1416. Translate MongoDB status descriptions and correct card-field ordering terminology and movement labels. Add semantic and browser regression coverage.
Thanks to xet7 !
Review source keys 814–929. Translate older English Todoist and Planner instructions, restore Jira pagination and Excel sheet guidance, and correct Taskwarrior and Focalboard label terminology. Add semantic checks and six browser cases.
Thanks to xet7 !
Review source keys 590–813. Restore literal escape examples and plain-bullet Markdown import guidance; distinguish unused disk space from price and data fields from agricultural fields. Add semantic and browser regression coverage.
Thanks to xet7 !
Record guarded meaning corrections and validation. Archive the oldest retained release by size and repartition October archives without changing released entries.
Thanks to xet7 !
Review source keys 220 through 454 and fix nine misleading labels covering avatar limits, historical time, grouping, pulse, unread comments, due dates, account anonymization and card labels. Add guarded corrections and a read-only popup-title browser case.
Thanks to xet7 !
Review the first 220 source strings and correct four semantic errors: reversed title arguments, automatic template application, the positive-integer height constraint and matching-card search scope. Record guarded repairs and extend the activity browser case.
Thanks to xet7 !
Translate the final 72 English placeholders and promote the full-inventory no-placeholder check. Preserve cancellation, discard and rollback constraints, already-applied changes and foreign-board protection. Register six existing recovery browser cases for Southern Sotho; fluency and runtime UI review remain pending.
Thanks to xet7 !
Fill 45 source-report, retention, estimate-mapping, email-failure and planning placeholders. Preserve omitted-versus-null handling, access requirements and no-resume/no-undo warnings; extend semantic checks and the sync-planning browser case.
Thanks to xet7 !
Fill 95 math, procedure, accessibility and shortcut placeholders. Verify random bounds, degrees rather than radians, return values, disabled definitions and screen-reader toggles. Mathematical and programming terminology remains lower confidence pending native review.
Thanks to xet7 !
Fill 90 placeholders for list sorting, Boolean logic, arithmetic and statistics. Verify strict and inclusive comparisons, logical negation, copy behavior, constants and numeric bounds. Statistical and trigonometric terminology remains lower confidence pending native review.
Thanks to xet7 !
Fill 110 placeholders for input labels, keyboard navigation and list operations. Check numbered tokens, dividend/divisor, get/remove distinctions, copy semantics, index direction and not-found behavior. Programming terminology remains lower confidence pending native review.
Thanks to xet7 !
Fill 75 editor, accessibility, colour and control-flow placeholders. Preserve 18 physical keyboard legends with exact-source exemptions and check numbered placeholders, loop conditions and bounds. Variable and programming terminology uses descriptive wording with lower confidence and needs native review.
Thanks to xet7 !
Fill 46 placeholders for delivery timing and grouping, workflow states, LDAP sync, card field visibility and subtask completion. Preserve token inventories and positive and negative permission semantics; extend notification and subtask browser cases.
Thanks to xet7 !
Fill 55 placeholders for member mapping, CSV columns, batch import and export, Trello access, link rules and webhook payloads. Preserve substitution tokens, literal interchange markers and permission limits, and extend the existing CSV and member-mode browser cases.
Thanks to xet7 !
Fill 22 instructions, retaining file formats, literal menu and column names, field mappings, first-board selection and excluded content. Extend semantic checks and register the existing import-help browser cases for Southern Sotho.
Thanks to xet7 !
Fill 34 English placeholders, preserving link directions, shared-field matching, unavailable-card privacy and edit permissions. Extend translation checks and both existing card interaction browser cases.
Thanks to xet7 !
Replace five remaining Italian labels in recovery and email controls. Preserve existing translated recovery warnings and email permissions, and cover the refresh labels in the recovery browser case.
Thanks to xet7 !
Record reviewed operations, mathematical regression checks and pending Corsican Blockly browser execution in Upcoming and the translation audit.
Thanks to xet7 !
Preserve recognizable physical key names and numbered placeholder inventories, including multiplicity. Correct the map floor-plan example and add Corsican to the existing map browser suite.
Thanks to xet7 !
Record full methodology translations, lower-confidence statistical terminology, passing checks and pending browser execution in Upcoming and the translation audit.
Thanks to xet7 !
Replace abbreviated substitutes with forecast assumptions, missing-history behavior and date fallbacks. Repair WIP group and authentication wording and register five chart-help browser cases. Corsican statistical terminology is lower confidence and needs native review.
Thanks to xet7 !
One executable per platform: that platform's published bundle repackaged
as a Node.js single executable application whose ZIP Node 26.11 mounts as
a virtual file system (useVfs + vfsArchive). Only FerretDB is copied out,
once and verified, to wekan-files/bin; native addons load from the VFS.
Everything WeKan writes is in wekan-files beside the executable, which
WRITABLE_PATH points at: attachments, avatars, db, bin, backup, logs,
versions. WeKan now treats a WRITABLE_PATH named wekan-files as the files
root (models/lib/filesRoot.js, shared by every place that derived it).
Started in a terminal it shows a menu: start, stop, status, version info
as Admin Panel / Version shows it, update to the newest release or change
to any other (checksum verified, backed up first, the old executable
kept), backup to wekan-files/backup/YYYY-MM-DD_HH-MM-SS, run at boot
(systemd, launchd, Task Scheduler, cron, Termux:Boot), log, settings.
Every entry is also a command. start runs the start-wekan.sh loop:
recovery, requested restore, FerretDB, recovery page, WeKan, restart.
Meteor's main.js chdir()s into the bundle, which a VFS cannot be, so the
server role keeps a virtual working directory. Node 26.11.1 breaks
fs.writev/readv while any VFS is mounted; they are rebuilt on fs.write
and fs.read.
single-file.yml builds every target in releases/single-file/targets.json
from its verified bundle (Macs on a Mac, to re-sign), starts it where a
runner exists with browser tests on amd64, and attaches what started.
Release All and Release All Missing call it.
Thanks to xet7 !
Record passing parser and translation regressions, conditional browser coverage and preserved Chinese values in Upcoming and the translation audit.
Thanks to xet7 !
Preserve repair limitations, acknowledgement scope and scoped-import choices. Test the native card-number search keyword and register conditional report-help browser coverage.
Thanks to xet7 !
Record passing regressions, preserved Chinese values and the current filtered translation backlog in Upcoming and the translation audit.
Thanks to xet7 !
Record restore and cloud configuration repairs, passing regressions and pending browser execution in Upcoming and the translation audit.
Thanks to xet7 !
Preserve restore modes, credential alternatives, provider menu names and migration states. Extend language regressions and register cloud-help browser coverage.
Thanks to xet7 !
Restore automatic loading instructions and the actual irreversible account-anonymization confirmation. Preserve feature scopes, backup boundaries and literal markup, with regression checks and browser help coverage.
Thanks to xet7 !
Record guarded scheduling and storage repairs, passing regressions and pending browser execution in Upcoming and the translation audit.
Thanks to xet7 !
Record regression coverage, pending lockout browser execution and preservation of existing Chinese values in Upcoming and the translation audit.
Thanks to xet7 !
Distinguish card recurrence from reset and known-user password failures from nonexistent usernames. Repair upload and support messages and register lockout-settings browser coverage.
Thanks to xet7 !
Record parser regression coverage, pending browser execution and preservation of existing Chinese wording in Upcoming and the translation audit.
Thanks to xet7 !
Record parser checks, pending browser execution and preservation of existing Chinese translations in Upcoming and the translation audit.
Thanks to xet7 !
Preserve executable search syntax and the direction of recent-date filters. Verify translated predicates with the search parser and register overdue-search browser coverage.
Thanks to xet7 !
Preserve authorized-board and member-or-assignee scopes, result counts and parser abbreviations. Add guarded repairs and shared-template guidance coverage.
Preserve authentication prerequisite identifiers, old and new date values, deletion warnings and directional placement. Add guarded repairs and passwordless-settings UI coverage.
Restore set-or-changed date conditions and assignee trigger wording. Preserve checklist negations and destination direction, with guarded repairs and date-trigger UI coverage.
Preserve import limitations and unmapped-node reporting, trigger/action distinctions, scheduling direction and completion negation. Extend guarded repairs and workflow UI coverage.
Preserve distinct upload and download limits, separate API buffering safety caps, WIP recovery choices and notification scope. Restore invariant unit labels and add attachment-limit UI coverage.
Preserve member-removal notifications, unsaved-description recovery and overtime semantics. Keep the Pomodoro technique name unchanged and add guarded repairs and shortcut UI coverage.
Distinguish personal overrides from cancellation and retain assigned-only access, notification conditions and deletion warnings. Add guarded repairs and an archive-tab browser case.
Translate stale English Todoist and Planner instructions against current source. Preserve importer mappings, field names and attachment limits, and add six importer browser cases.
Keep email placeholders and account actions distinct, preserve role restrictions and explain that enabling permanent deletion does not itself delete content. Add guarded repairs and custom-field UI coverage.
docs/Platforms/FOSS/OS/BSD.md now lists the patches each BSD's packagers
keep for Node.js - FreeBSD ports www/node26, OpenBSD ports lang/node,
pkgsrc lang/nodejs (already 26.11.1), DragonFly DeltaPorts - and what WeKan
does with each, plus Bun, sharp/libvips, bcrypt and FerretDB on each BSD, and
how to install on OpenBSD (under /usr/local, wxallowed) and NetBSD (paxctl).
bsd.yml: the OpenBSD job mounts its workspace wxallowed before starting the
bundle there, and the NetBSD job compiles the native modules with pkgsrc's
GCC 14, as Node 26 needs C++20. Both build by themselves once wekan/node-
patches publishes node-openbsd-* and node-netbsd-x64.
Thanks to xet7 !
Restore directional controls, comment and worker permission limits, and the deliberately empty comment placeholder. Preserve newer translations through guarded repairs and extend the color-rule browser case.
Specialized color shade paraphrases are lower confidence and remain open to native-speaker refinement.
Preserve imported roles and permission limits, favorite toggles, automatic width and card inactivity thresholds. Add guarded repairs and regression coverage for profile and font controls.
docs/Platforms/FOSS/OS/BSD.md says where FreeBSD, OpenBSD, NetBSD and
DragonFly BSD stand - the Bun bundles FreeBSD already has, which FerretDB
and Node.js binaries exist for each, and the plan, easiest first.
.github/workflows/bsd.yml, called by Release All, builds WeKan's Node.js
bundle for a BSD: it takes the release's amd64 bundle, verifies it and adds
that BSD's Node.js (wekan/node-patches) and FerretDB, then inside a VM of
that BSD compiles the native modules, runs the same bundle steps as every
other platform, and starts the bundle with its own start-wekan.sh, which
must answer HTTP 200 before the zip is attached. FreeBSD x64 builds now,
from node-freebsd-x64 and ferretdb-freebsd-amd64. FreeBSD arm64, OpenBSD
amd64/arm64 and NetBSD amd64 are skipped, not failed, until node-patches
publishes their Node.js. The Node.js resolver, the native addon ELF check
and the platform packages step know the BSD names.
Thanks to xet7 !
Besides Amazon SES, WeKan now takes the bounce and spam-complaint
webhooks of the other common sending services, at
/api/inbound-email/bounces/<service>, each proven the way that service
signs: Mailgun's HMAC-SHA256, SendGrid's ECDSA signature over the raw body,
Mandrill's HMAC-SHA1 over the URL and fields, Resend's Svix signature; and
for Postmark, Brevo, Mailjet and SparkPost, which do not sign, a password
in the webhook address (Basic auth or ?secret=). Each service's events
become WeKan's hard, soft, automatic-reply and complaint categories, so the
same Admin Panel rules act on them. Bounces -> Other sending services
turns each on with its key or password, written only when typed and never
shown back; a forged or unsigned request is refused and logged in Problems
-> Security. These endpoints, like the SES one, work without WITH_API.
Mail systems with no webhook keep working as before: their bounce emails
reach WeKan by IMAP and are recognised as delivery status notifications.
Thanks to xet7 !
Repair 37 mixed-language labels and descriptions, including account anonymization and logged-in user access. Preserve role restrictions and archive semantics. Regional phrasing remains provisional pending fluent review.
Email that comes back is handled in WeKan instead of landing in the
administrator's own inbox. Admin Panel -> E-mail -> Bounces: hard bounces
(a mistyped invited address), soft bounces, automatic replies and spam
complaints, each with actions done after a count - list it, stop emailing
the address, tell who invited it, delete the invitation, ignore - as
listmonk and Discourse do it, and the list of addresses that bounced, which
an admin clears to email them again. Amazon SES Bounce and Complaint
notifications and mail SES receives arrive through SNS at
/api/inbound-email/ses, signature-checked against Amazon's certificate and
accepted only from the topics the Admin Panel lists. Delivery status
notifications and out-of-office replies arriving by IMAP, a provider
webhook or a Sandstorm grain are bounces too, never cards.
Admin Panel -> Problems -> Email shows what went wrong sending or
receiving: a send the mail server refused or could not reach, an IMAP
mailbox that cannot be read (never with its password), an unreadable
email, an email for a board that no longer takes email. Attempts stay in
Problems -> Security. The inbound email endpoints no longer need the whole
REST API turned on.
Runtime tests run WeKan's own transports against local SMTP and IMAP
servers over TCP, a real Sandstorm Maildir, the webhook over HTTP and
signed SNS messages; UI tests drive the Email and Chat views, the
email-to-card settings and the Bounces page. They found and this fixes:
every whole-message parse failed in the server bundle (nodemailer's
addressparser resolved to its ESM build), the Chat view's drag and drop
never started (dragscroll took the mouse) and dropped a card into the
target's previous list, and the Chat panes did not follow a change to a
right-to-left language.
Thanks to xet7 !
Fixes#794,
Fixes#3948.
Fill 30 English placeholders and cover link direction, edit permission, paused links and inaccessible cards. Regional phrasing remains provisional pending fluent review.
Fill 30 English placeholders while retaining existing translations. Verify matching fields, propagation direction, edit permission, unavailable cards and self-link refusal. Extend the existing attachment and field-link browser flows to Maltese.
Complete Khmer and its aliases, Burmese, Amharic, Yiddish, Bhojpuri, Maithili and Konkani notes. Preserve the Khmer symlink, map its browser tag, and check Yiddish RTL layout.
Technical prose in these languages is provisional pending fluent review. Preserve existing Chinese values and unrelated new feature translations.
Email shows the board as a Gmail-like mailbox in the board's own theme:
the Inbox is the list email to card puts new cards in, Sent and All mail
follow the cards' email, the board's lists are folders and its labels are
Gmail labels, coloured on every message. The messages are the shared
table page, newest first, paged on the server; a message opens as the
card popup, its comments the conversation. Compose and Reply send From
the member's own board address, so the answer returns to this board, and
by In-Reply-To to the same card as a comment.
Chat shows the board as a Slack / Rocket.Chat workspace in the current
theme: a tree of Workspaces, boards, swimlanes, lists and cards as
channels; the selected card in the middle; its comments as chat on the
other side. Tree | card | chat left to right, mirrored right to left.
Each pane folds, the two borders drag (and move with the arrow keys),
and widths and folds are saved on the member's profile. Swimlanes, lists,
cards, workspaces and boards reorder by drag and drop through the same
paths the board and All Boards use.
The new strings are translated in every locale.
Thanks to xet7 !
Use Hiragana for the Japanese Hiragana catalog and its registered browser language tag. Extend direction checks to Sorani, Pashto, Sindhi and Uyghur. Record exact before/after repairs and all four source distinctions.
Kurmanji, Sorani, Pashto, Sindhi, Uyghur, Hausa and Somali technical phrasing is provisional pending fluent review. Existing Chinese values remain unchanged.
An email to a member's personal board address, board+<token>@<domain>,
becomes a card on that board, as Trello's email-to-board does (#794,
#3948). The subject is the title; the body (quoted replies stripped, HTML
as text) is the description, with a From line; attachments are attached
(no SVG, as for uploads). The author is the address's member, never the
From address, and they must still have write access when the email
arrives. A reply whose In-Reply-To/References matches a card's thread
becomes a comment on it.
Notification Settings, at three levels: the Admin Panel turns it on and
holds the domain, the provider webhook secret and the IMAP mailboxes with
their passwords (never published, never sent back to a browser); a board
admin turns the board on and picks the list and swimlane; board and
member choose which parts of an email are used. The member sees and
replaces their own address.
It arrives by POST /api/inbound-email (Mailgun, SendGrid, Postmark, JSON,
forms, multipart with attachments, raw MIME), by IMAP polling with
imapflow and mailparser under a database lease so one replica polls, or
on Sandstorm through the grain's own address: the bridge's Maildir is
read, a member's board token in the subject routes it, and WeKan's
outgoing email goes through HackSessionContext.send, since a grain has no
network. The email and hack-session capnp schemas are generated with the
other Sandstorm bindings.
Unknown addresses, wrong webhook secrets and over-limit addresses are
logged under authn.inbound-email. Rate limit per address, size limits,
Message-ID deduplication. The new strings are translated in every locale.
Thanks to 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 !
Repair 38 values in each of the az, az-AZ and az-LA catalogs. Use lövhə for a Kanban board rather than şura (council), distinguish the assigned person, and correct the card-to-board command. Preserve query literals, invitation tokens and visibility emphasis; keep activity predicates compatible with the actor prefix.
Record all 114 exact corrections. Terminology and correction-ledger checks pass, including newer-translation preservation. Three browser cases register but have not been executed.
Thanks to xet7 !
Translate 193 current placeholders and nine labels hidden by fill exemptions. Preserve existing non-English values. Omit the separate linking verb before already inflected Kannada action predicates; retain the helper behavior for other languages. Use numerator/denominator notation for fixed-order progress counts.
Check permissions, import exclusions, recovery consequences, source literals, locale boundaries and runtime interpolation. Register six Kannada browser cases. Longer technical wording and full rule clause grammar remain open to speaker review.
Thanks to xet7 !
Translate 193 current feature placeholders and the exempt Bytes label, preserving all existing non-English values. Check field directions, permissions, import exclusions, recovery consequences, source literals and runtime interpolation. Register five Thai browser cases for the affected feature flows.
Longer technical wording remains provisional pending speaker review.
Thanks to xet7 !
Both v12.39 Mac apps were skipped although their bundles were attached:
`gh release view ... | grep -q` let grep exit at the first match while gh
was still writing, gh got SIGPIPE, pipefail failed the pipeline, and the
job said the bundle had not been published. The Windows EXE, Docker image
and snap-launchpad checks had the same shape. Each now reads the asset
list whole before matching it.
Thanks to xet7 !
The armhf and s390x builds succeeded, then the runner's connection to the
Launchpad API dropped or a poll hung, and snapcraft ended each attempt as
"Network is unreachable" or "Timed out waiting for build". A failed
build-state poll is now retried for up to 30 minutes; other errors and the
workflow's own deadline are unchanged.
Thanks to xet7 !
Both FreeBSD jobs stopped at "native check: sharp": Sharp's only FreeBSD
prebuilt is WebAssembly with Worker threads, and under Bun on FreeBSD the
first image operation never returned, while the same build works under
Bun on Linux. Sharp is now compiled against the system libvips there, as
on Android, and the FreeBSD VM installs vips.
Thanks to xet7 !
Export to Trello: Board Settings -> Export -> Trello creates the board in
Trello through its API with the saved key and token: labels, lists, cards,
checklists, comments, attachment files, and archived lists and cards. It
runs as a server job from the board export, with the same access check.
Swimlanes: Trello has none, so each swimlane is exported as a label named
"Swimlane: <title>". The Swimlanes by Blue Cat Power-Up groups Trello cards
into rows by label, and every Trello import turns those labels back into
swimlanes.
Sandstorm: a grain has no network of its own. Allow access to Trello opens
the Sandstorm Powerbox for https://api.trello.com/1, like the Sandstorm
WordPress app reaches wordpress.org; the access token is kept per user on
the server, and Trello import and export go through the bridge proxy.
Thanks to xet7 !
Translate 193 current feature placeholders and the exempt Bytes label. Correct four existing calendar labels using the long vowel already used elsewhere in the catalog, with exact ledger entries. Cover permission direction, import exclusions, recovery consequences, Trello markers and runtime interpolation; register Tamil feature and calendar browser cases.
Longer technical wording remains provisional pending speaker review.
Thanks to xet7 !
Fill eleven new Marathi strings without changing existing values. Extend Bengali checks for the concurrently supplied translations and register Bengali and Marathi export-dialog browser cases.
Thanks to xet7 !
Translate 193 Nepali placeholders and 11 new Trello/Sandstorm strings in each of nine Chinese catalog paths. Preserve all existing non-English values, including every earlier zh translation. Check import exclusions, permissions, recovery consequences, literal swimlane markers, exact tokens and runtime interpolation. Register browser coverage without contacting Trello.
Thanks to xet7 !
Fill 200 English MongoDB and S3 values in Nepali, Kannada, Malayalam, Javanese, Kazakh, Kyrgyz, Tajik and Somali, using the existing catalogs for terminology. Preserve existing translations and verified zh wording.
Thirteen locale and supplemental-audit suites pass, including token, state and runtime checks. Fourteen storage-settings browser cases register; a running rebuilt app is still needed. Complete software compounds remain provisional pending speaker review.
Thanks to xet7 !
Fill 100 English MongoDB and S3 values in Bengali, Icelandic, Tagalog and Albanian. Repair 15 mixed English/Tagalog clauses, retaining literal AWS console labels and the one-time secret display warning. Preserve all other translations, including existing zh wording.
Locale, correction-ledger, supplemental audit and runtime checks pass. Six storage-settings browser cases register; execution needs a running rebuilt app. Complete software wording remains provisional pending speaker review.
Thanks to xet7 !
Update Upcoming and TODO Later with the filtered backlog and the additional review queue. Keep native-language review and browser execution explicitly open.
Thanks to xet7 !
Fill 212 current placeholders and 55 English labels hidden by technical-name and short-word exemptions. Keep existing Marathi translations and preserve source tokens, product names and physical keyboard legends.
Add a read-only audit for product-prefixed prose, generic labels and keyboard names. Candidates require language-specific review; existing human-verified zh wording must not be overwritten.
Marathi, supplemental audit, shared-label and Scrum recovery checks pass. Register Marathi linked-field, notification, recovery and storage-settings browser coverage. Browser execution and broader native language review remain unfinished.
Thanks to xet7 !
Update Upcoming and TODO Later after the current Bengali fill list became empty. Preserve the distinction between filled strings and completed language-quality review.
Thanks to xet7 !
Fill 182 English placeholders for linked fields, import formats and mapping, notifications, webhooks, LDAP, Wrike and recovery. Preserve existing Bengali wording. The full current fill inventory is empty; older wording and complex technical phrases still need native review.
Verify exact placeholders, permissions, import exclusions, recovery consequences and i18next interpolation. Three focused suites pass. Four Bengali browser cases register; execution needs a running application.
Thanks to xet7 !
Track remaining fills and language-quality review separately. Document preservation of human-verified Chinese wording and the unavailable browser app.
Thanks to xet7 !
Fill 3,129 English values and repair 167 seeded values across 130 locale paths. Preserve existing translations and human-verified zh catalogs. Retire 15 completed pending keys.
Record provisional terminology in Luganda, Tswana, Tongan, Tsonga, Aymara, Quechua, Wolaytta, Venda, Acehnese, Walloon, Wolof and Tamazight as low confidence pending native review.
All 307 translation/Blockly suites and 21 human-preference checks pass. Source tokens match across all 246 catalogs. Expanded browser coverage registers 125 tests; execution requires a running app.
Thanks to xet7 !
A binding stored with issuer, nameID and nameIDFormat but no nameQualifier
or spNameQualifier keys was not found by identity, because a query for ''
does not match a missing field. The user was then found by username and
refused as account-already-bound-to-saml. An empty assertion qualifier now
also matches a missing one, and the next login writes them as empty
strings. A non-empty qualifier and a missing issuer still fail closed.
Thanks to xet7 !
tests/buildScriptParity.test.cjs failed because the Bun target-build
scripts, which bun.yml runs on each target runner, and the UCS migration's
docker-exec.mjs are not build.sh menu entries. They are listed as internals
with the reason, like the other release-workflow helpers.
Thanks to xet7 !
Android: Termux's user does not own the bind-mounted bundle, which the
workflow makes writable for everyone, so chmod failed with EPERM before an
allowed write. Files are now chmodded only when not writable already.
FreeBSD: the final Bun native check was killed at its deadline with no
output, its buffered console lost with it. It now writes each step
unbuffered and exits explicitly, and the supervisor tells a hung operation
(failure, naming the step) from every check passing and Bun then hanging on
exit (oven-sh/bun#44595), which passes with a warning.
windows-arm64 built and attached its bundle but showed an error: the
expected no-prebuilt-addon probe printed Bun's raw "error:" line, which
GitHub turns into an annotation. It is now reported as one plain line.
Thanks to xet7 !
The Docker job's bundle-version check looked for "name":"wekan" in app.js,
but rspack keeps only the version field the server reads, so the check was
skipped with a warning on every release. It now also reads the one
v-prefixed version-only module, and still only warns when that is absent
or ambiguous. A cancelled snap-launchpad job no longer claims it ran out of
time: the v12.38 jobs were cancelled at 1h49m of a 360-minute cap.
Thanks to xet7 !
The Artifact Hub scan of the v12.38 image found advisories in browserslist,
baseline-browser-mapping, colord, svgo, source-map-js (minifier-css),
compression and qs (webapp, oauth) and lodash.template (simple-schema), all
fixed in the same major. bump-bundle-npm-deps.mjs now raises them, and
gives a bumped package its own copy of a dependency whose sibling is a
different version, so compression 1.8.2 runs with the bytes, negotiator and
safe-buffer it declares. nodemailer and postcss-selector-parser fixes need a
new major and are recorded as not fixable.
Thanks to xet7 !
Debian's docker-cli carries Go 1.24.4, whose standard library advisories
were most of the Quay and Artifact Hub scans of the image, and only the UCS
migration used it, for docker exec. The migration's mongo and mongoexport
wrappers now run releases/ucs-migration/docker-exec.mjs, which speaks the
Docker Engine API over the mounted socket with the same stdout, stderr and
exit status, and the image installs no Docker CLI.
Thanks to xet7 !
Every refusal to link a SAML login to an existing (e.g. LDAP) account threw
the same "SAML identity does not match this account" and logged nothing.
Each condition now has its own reason in the server log, and a missing email
or email_verified=true attribute in the assertion is named on the sign-in
page. The refusal still fails closed. With merging on, a login name that is
the assertion's email also finds the account by a verified email, instead of
trying to create a new user. email_verified, the matching rules and the
unused SAML_ATTRIBUTES are documented.
Thanks to xet7 !
Browsers end a script at </script foo="bar"> too, so an end-tag pattern
written as <\/script\s*> or <\/script> misses it. The reported test, the
server input sanitizer's style pattern and one more test now use
<\/tag\b[^>]*>, and tests/securityAlerts20261012.test.cjs fails on the
loose shape for script, noscript, style, iframe, textarea and title
anywhere in the tree.
Thanks to GitHub CodeQL and xet7 !
The starredPublicBoards publication only sent public and instance boards,
so a user whose favorites are all private boards saw an empty popup. It now
sends every starred board the user may see - public/instance, or an active
membership, organization, team or domain share - through the shared
visibility builder, so a revoked share or stale star still never brings a
private board back.
Thanks to Raymond-Voogt and xet7 !
Fixes#6764.
The list drop placeholder had no --list-width, so none of the list width
rules matched it and it kept flex-shrink: 1 with no content; in a swimlane
wider than the window flexbox shrank it to a thin shadow. It now never
shrinks, every list sortable pins its min-width to the dragged list's width,
and it is drawn as a tinted, dashed drop area.
Thanks to mimZD and xet7 !
Fixes#6763.
Preserve Android build metadata through Termux's environment reset and build Sharp against libvips. Bound native addon probes and final checks, read complete AppImage asset lists, and follow authenticated chart pagination. Keep expected negative smoke diagnostics out of CI error annotations.
Thanks to xet7 !
Include development and optional dependencies in the locked npm install and verify the Meteor Rspack config before compilation. Exercise both omitted and included dependency installs with an offline fixture.
Build one Meteor ESM application, rebuild native addons on each target, and attach verified archives and checksums. Use native hosted runners, Alpine and Termux containers, and FreeBSD VMs. Cover release planning, failed-build gates, native modules, HTTP/DDP, and Chromium signup and board creation.
Thanks to dupontbertrand and xet7 !
The jobs run compile-native-addons.mjs from programs/server with the bundle
as `../..`, and node-gyp runs with each package as its working directory.
The relative node-gyp path was resolved from there, so v12.34's build-win32
and build-win-arm64 failed with "Cannot find module ...accounts-password/
programs/server/node_modules/node-gyp/bin/node-gyp.js". The bundle path is
made absolute first. The new test calls it the way the jobs do, with a fake
node-gyp that resolves paths the way the child process does, which the old
fake did not.
Thanks to xet7 !
Alert 565 (ReDoS): the package-specifier pattern in
releases/prune-unreachable-npm.mjs allowed `/` both between and inside path
segments, so a quoted `-/-/-/...` could be split in exponentially many ways;
es-module-lexer's minified lexer alone took over two seconds. A `/` now only
separates segments. Over the 23,812 files in node_modules it finds every
package it found before, and also `require('querystring-es3/')`, the
trailing-slash form the old pattern missed.
Alert 566 (bad tag filter): the Sandstorm readiness test matched <script>
case-sensitively. It and three negative checks that said "no <script" in
sanitized output now match any case; one of those also collected tag names
with a case-sensitive pattern that an <SCRIPT> tag would have escaped.
Alerts 567 and 568 (code sanitization): a test wrote dictionary paths into
generated JavaScript with JSON.stringify. It copies the dictionaries beside
its entry so the source is constant, and four more test probes take module
paths as arguments instead. The i18n loader the test drives, which writes an
asset name into generated server code, refuses a name outside a locale
tag's characters and escapes the literal.
tests/securityAlerts20261012.test.cjs fails on any case-sensitive script,
noscript or iframe pattern and on JSON.stringify spliced into an import,
require or asset path anywhere in the tree.
Thanks to GitHub CodeQL and xet7 !
Merging #6761 left a stray "This release fixes the following bugs:" above
the security header and the Sandstorm entries ahead of the security fixes.
Security comes first again, the Sandstorm entries follow the bug fixes under
one topic line with their contributor credited, two one-word wrapped lines
are reflowed, and In short is back to four sentences.
Thanks to fvb-sb, mnutt and xet7 !
Since v12.15 the LDAP server's certificate is verified by default, and
Node.js verified it only against the Mozilla list it is built with. A
company root CA installed on the server with update-ca-certificates was
unknown to WeKan, so after upgrading from v12.14 the snap refused LDAPS
logins with UNABLE_TO_VERIFY_LEAF_SIGNATURE.
server/main.js now adds the operating system's CAs to Node's own before any
application code runs: tls.getCACertificates('system'), and the distribution
CA bundle files Go's crypto/x509 reads. The bundle file matters in the snap:
on Debian and Ubuntu snapd keeps the host's /etc/ssl, but the per-CA links
update-ca-certificates makes point into /usr/local/share/ca-certificates,
which the snap sees from its base. Verification is not relaxed - a CA the
system does not have is still refused - and --use-bundled-ca keeps Node's
list only. It covers LDAPS, mail, webhooks and OAuth2 in every launcher.
A refused LDAP certificate now says what to do, in the log and in Admin
Panel's Test connection: install the CA on the server, or set LDAP_CA_CERT.
No hint suggests turning verification off.
tests/systemCaCertificates6762.test.cjs runs a TLS server with a certificate
from a test private CA (its key is not kept) and checks the reported error
without the CA, verification with it in the system bundle, refusal without
it, --use-bundled-ca, and the hints. Two LDAP guards follow the extended
error handling; prune-unreachable-npm.mjs leaves the regex guard's reviewed
list, as it no longer builds a RegExp.
Thanks to fvb-sb and xet7 !
Fixes#6762.
Strengthen dependency analysis and expose retention reasons in an audit
report. Prune verified browser inputs, unused client polyfills and server
type declarations. Include safety regressions and packaged-runtime results.
Load SAML, LDAP and transliteration implementations when their operations
need them. Preserve registration and authentication checks, with tests
and documentation accompanying the change.
Extract inline maps during bundle trimming so startup avoids scanning
large base64 comments. Preserve mapped stacks and include packaging
regressions and measured production results.
Keep English embedded and load other server dictionaries on first use,
without changing client loading or translation contents. Include loader
regressions, build integration checks and profiling documentation.
Load archive libraries only when an import, export or restore needs them.
Keep lightweight timing enabled and make detailed Meteor profiling opt-in.
Include first-use regression checks and production measurements.
Add launcher timings and a local production-path profiling runner. Replace
the fixed database delay with listener readiness and notify the waiting
page through a held request. Include tests and profiling documentation.
fetch-mongo-tools.sh, the local build-release-bundle.sh and the
check-arch-binaries.sh preflight fell back to the eight separate
<tool>-<platform> binaries when a release had no mongo-tools-<platform>.
wekan/mongo-tools-patches now publishes only the one program, so every
bundle, image and snap gets that or no tools, and says so. The Windows and
macOS jobs also remove the inherited amd64 mongo-tools with the launchers.
tests/fetchMongoTools.test.cjs serves a release that has only the per-tool
binaries and checks that none is installed or even requested, and fails on
any script or workflow that builds a per-tool download address.
Thanks to xet7 !
Alert 564 (regex injection): releases/platform-native-packages.mjs compiled
its command-line platform and arch into a RegExp. The canvas package names
are now compared as strings. The upload naming of attachments and avatars,
the attachment rename popup and tools/mirror-menu.mjs built a RegExp from a
file extension or a name the same way; they use literal suffix and prefix
checks now. tests/noUnescapedDynamicRegExp.test.cjs parses every source file
and fails on a RegExp built from a value that is not escaped, unless the
site is listed with the reason its values are constants.
Alert 563 (incomplete escaping): name.replace('/', '%2F') replaced only the
first slash. registryPackagePath() replaces every one and refuses anything
that is not an npm package name. Seven tests escaped a regex the same
first-only way; 'g++'.replace('+', '\\+') let "- g+" pass a guard. They use
the complete escapeRegExp, and tests/noIncompleteRegExpEscaping.test.cjs
fails on any string-pattern replace that performs an escape.
Alert 562 (ReDoS): the CasaOS reverse-domain id pattern in
tests/platformTemplates.test.cjs let '.' be matched by both groups, so
'0.0.0...!' backtracked exponentially. Each separator now belongs to one
group, and the test runs CodeQL's attack string against it.
Thanks to GitHub CodeQL and xet7 !
They read every page of a GitHub list with gh api --paginate --slurp,
and --slurp exists only from gh 2.48 on: an older gh stopped the release
with "unknown flag: --slurp". --paginate alone prints the pages back to
back, and gh_pages() now reads them one after another. A truncated page
is still an error, never a shorter list.
The tests feed the scripts pages the way gh prints them, check that no
gh call asks for --slurp, and (negative) that a truncated page fails.
Thanks to xet7 !
On Univention WeKan is served at /wekan. profile.avatarUrl holds
/cdn/storage/avatars/<id> - what Change Avatar and the server's own
migrations store - and it was rendered as stored, so the browser asked
the UCS web server for a path outside WeKan, got 404, and every avatar
was an empty circle, while Change Avatar, which builds its own link,
showed the picture. models/lib/avatarDisplayUrl.js treats the stored
value as the avatar's name: whatever host or old path prefix stood
before /cdn/storage/avatars/ or /cfs/files/avatars/ is replaced with the
path WeKan is served at now, relative to the page's origin. The avatar,
the People page and the report tables draw through it; the default
avatar gets the prefix too. External pictures and data: URLs are left
alone.
The same session, opened through 127.0.0.1:8443/wekan, fetched lazily
loaded code from ROOT_URL's host ucs-2275.wekan.intranet, which the
browser could not resolve (NS_ERROR_UNKNOWN_HOST, "Cross-Origin Request
Blocked"). Meteor's dynamic-import setting useLocationOrigin is now on
by default, so that code comes from the page's own origin and ROOT_URL
path; an explicit value in METEOR_SETTINGS still wins. Links keep using
ROOT_URL as before.
tests/avatarDisplayUrl.test.cjs covers every stored shape, (negative)
leaves external, data: and query-string URLs alone, checks that no
template puts a stored avatar URL in src without avatarSrc, and pins the
dynamic-import default and its override. The Playwright spec
avatar-sub-path.e2e.js checks the drawn src and the setting in a browser;
it was not run here.
Thanks to xet7 !
The eight tools were eight Go programs, each with its own copy of the Go
runtime, the MongoDB driver and the tools' shared packages: about 105 MB
in every bundle, image and snap. wekan/mongo-tools-patches now also
publishes mongo-tools-<platform>, all eight in one program of about 15 MB.
releases/fetch-mongo-tools.sh puts it in a bundle with a launcher per
tool - a three-line shell script on Linux and macOS, a .cmd on Windows -
so mongodump keeps working from $SNAP/bin, the bundle and PATH. Launchers
rather than symlinks, because a symlink has to survive zip -r, Docker
COPY, snapcraft and every user's unzip. Until the newest
mongo-tools-patches release has mongo-tools-<platform>, it fetches the
eight per-tool assets as before. It first removes the tools a repacked
bundle inherited from amd64, so a platform without tools ships none
rather than the wrong CPU's.
Every bundle job in release-all.yml, repack-bundle-for-arch.sh and
build-release-bundle.sh use it; the snaps move mongo-tools into bin/
with its launchers; the telemetry audit scans mongo-tools as a tool; the
Windows single EXE unpacks the .cmd launchers; the preflight and the URL
check accept the one program.
tests/fetchMongoTools.test.cjs serves the release from a local HTTP
server and checks the launchers run the program with their arguments,
the Windows .cmd files, the per-tool fallback, and (negative) that
inherited amd64 tools are removed, that a checksum mismatch is refused,
and that no workflow or snap recipe downloads the tools itself.
releaseBinaryFreshness now finds the newest-release URL in the helper;
workflowFailureDiagnostics required an ::error:: beside each new exit.
Thanks to xet7 !
ed5502edfa brought server/sandstorm.js back, and with it a commented-out
v0.8 migration that sets permission: 'public'. It is a write, not a read
gate, so it cannot leave instance boards out; it is listed as reviewed.
Thanks to xet7 !
nodejs.org's node executables carry their full symbol table and DWARF debug
data, which only a debugger reads. Node 26.11.1 for linux-x64 is 150.0 MB
and 116.7 MB without them; linux-arm64 goes from 150.2 to 114.6 MB. Every
bundle, Docker image, snap, AppImage, Flatpak and the Sandstorm package
ships one.
releases/strip-node.sh runs after the download has been verified against its
source's checksum, so the file verified and recorded is the original:
- ELF: eu-strip (elfutils). GNU strip only reads the host's own CPU and
fails on an arm64 node on an x64 runner. Inside an emulated target
container the native strip is the fallback.
- Mach-O: strip -x keeps the 39006 exported symbols addons bind to and
drops the 901035 local ones. It then re-signs ad hoc, because stripping
breaks the signature and an arm64 Mac runs no unsigned binary.
- PE: nothing. node.exe has no COFF symbols, and its debug data is a
separate .pdb that is not shipped.
It refuses a result that lost .dynsym, the table native addons resolve
N-API against. The stripped linux-x64 node was run with bcrypt, argon2,
sharp and @napi-rs/canvas, and each hashed, verified or rendered correctly.
embed-verified-node.sh calls it for amd64, arm64, Windows, macOS, Sandstorm
and the local build, and install-node-for-arch.sh for the emulated
architectures. build-amd64, build-arm64 and the extra-arch containers
install elfutils.
tests/stripNode.test.cjs strips real gcc -g binaries (it checks that
.symtab and .debug_* are gone, .dynsym stays, and the binary still runs),
refuses an ELF without .dynsym and a non-binary, leaves a .exe untouched,
and pins every call site after its checksum check.
tests/buildScriptParity.test.cjs lists every releases/ script: it named
compile-native-addons.mjs, added by 34a33f7b14 and not registered, and now
strip-node.sh. Both are registered as release-workflow helpers, and the
suite passes again. Its earlier failure was reported as pre-existing; it was
not.
Thanks to xet7 !
Adds the Docker, Launchpad and history, and native-module entries to Upcoming, and notes in the released v12.33 sharp entry that its 32-bit ARM zips can crash on ARMv6 and some ARMv7 boards, and where the fix is.
Thanks to xet7 !
Inspecting the bundles to find what could be removed found a crash risk
added in v12.33 by 943bfd9197. That commit gave the armv6, armhf and armv7
bundles sharp's @img/sharp-linux-arm package. readelf shows its .node is
built for ARMv7 (Tag_CPU_arch: v7) and its libvips for ARMv8. The armv6
bundle runs on Raspberry Pi 1 and Zero, and the armhf/armv7 bundles on
ARMv7 boards. Executing an instruction the CPU lacks raises SIGILL, which
kills the whole WeKan process the first time a thumbnail is made. Before
that commit the bundles had only the linux-x64 package, which fails to load
and is handled. 32-bit ARM now downloads no native package and keeps the
WebAssembly sharp, which runs on any CPU (thumbnails and GIFs were made
with it under Node 26).
The same inspection found that @napi-rs/canvas is not dead weight. pdfjs-dist
6 builds a DOMMatrix while loading, so without canvas the import throws and
PDF attachment text is never indexed for search. This was checked: a PDF
made with pdfkit gives its text back with canvas and fails without it.
Every bundle except Linux x64 had only canvas-linux-x64-gnu, which
platform-native-packages then removed. The script now adds the glibc/MSVC
canvas package for the target as it does for sharp, verified by sha512 and
by its binary header. That covers linux x64/arm64/riscv64, darwin x64/arm64
and win32 x64/arm64. It never installs a musl package, nor anything on
32-bit ARM, where canvas is ARMv7 too. Where none is published (ppc64,
s390x, ia32, loong64), that is logged.
Checked against the npm registry: linux-arm64 and win32-x64 got sharp and
canvas, and both passed the ELF/PE check. linux-arm downloaded nothing.
tests/platformNativePackages.test.cjs covers 32-bit ARM, the canvas choice
(no musl, no ARM, none where unpublished) and the canvas download.
Thanks to xet7 !
Launchpad snap legs (95-104 minutes in v12.32): prepare-launchpad-source.sh
archived the whole tree, 237 MB of docs, tests, translations and client
code, for every architecture. The wekan part (source: .) then copied it
again on Launchpad's slowest builders, and the recipe uses none of it,
because the app comes from the release .zip. The snapshot now holds only
what snapcraft.yaml reads: snapcraft.yaml, snap/, snap-src/, and
releases/fetch.sh, check-telemetry.py, risk-baseline.json (its deny-hash
list) and migrate-mongodb-to-ferretdb.mjs. That is 1.1 MB.
tests/releaseSnapLaunchpadFlatten.test.cjs checks the snapshot holds the
listed files and nothing else, and checks against the real snapcraft.yaml
that every project path it reads is in the list.
Git history that was fetched and never read:
- snap-launchpad cloned the full history (fetch-depth: 0, 447 MB) because
remote-build rejects shallow repositories. What it uploads is the fresh
one-commit repository made from git archive, so a shallow checkout is
enough.
- bump only reads the tree and pushes one commit on top: shallow.
- prepare fetched history to see whether the release tag already existed.
It now asks GitHub with git ls-remote, which also works on a re-run.
- update-ondra-gantt-repos cloned ~160 MB of history per variant repository
to add one commit: shallow.
- release.yml (chart-releaser) ran on every push to main with a full clone,
but this repository has no charts/; the chart is published to
wekan/charts by release-all.yml. It now runs only when charts/ changes.
The wekan/charts checkouts keep fetch-depth: 0 because they need the
gh-pages branch, and that repository is 15 MB.
Thanks to xet7 !
In v12.32 "Build and push multi-arch image" took 27 of the docker job's 29
minutes. For each of seven platforms, six of them emulated, the Dockerfile
installed a compiler toolchain, resolved and downloaded Node.js, reinstalled
programs/server with npm, re-ran the prune, bump, trim and prune passes, and
purged the toolchain again. That platform's .zip leg in release-all.yml had
already done all of it, with the same scripts.
Stage 1 (FROM --platform=$BUILDPLATFORM) now runs natively on the runner for
each target. It downloads that target's .zip, checks it against the
release's own .sha256sum, re-runs the telemetry check, reads bundle/node's
ELF header to confirm the target CPU, and lays out /build and
/usr/local/bin/node. The image's node is the .zip's own node: the file the
leg resolved and verified, rather than a second download that had to match
it. Stage 2 is the only part that runs as the target CPU. It adds the WeKan
user, applies an OS update, installs file, libatomic1 and docker-cli, and
copies stage 1's result. No npm and no compiler ever enter the image.
The docker job read the base image from the first FROM; it now reads the
last one, since the first is the build-machine stage.
Stage 1 was run outside Docker against the real v12.32 s390x zip: the
checksum, the telemetry check (47979 files) and the ELF check (machine 22)
passed, and the layout was right. The ELF check rejects the same node when
expected as ppc64le, and rejects a non-ELF file. No Docker build ran here.
tests/dockerImageFromBundle.test.cjs pins the two stages, the verification,
the per-architecture ELF numbers (shared with check-addon-arch.mjs), and
that no reinstall, Node.js download or toolchain comes back. It fails on
the previous Dockerfile. Nine tests pinned the old in-image steps; each now
pins the new behaviour, with the reason recorded beside it.
Thanks to xet7 !
Adds an Upcoming section for it, and notes in the released v12.33 entries that their win32 and win-arm64 zips were not published, and where the fix is.
Thanks to xet7 !
v12.33's build-win32 and build-win-arm64 both failed their addon check:
...accounts-password/node_modules/argon2 has no arm64 Windows addon to load
The jobs relied on npm to compile the addons that have no prebuild for their
CPU (argon2 0.44 for win32-arm64; bcrypt and argon2 for win32-ia32) during
the programs/server install, through Meteor's npm-rebuild.js. The release
jobs use npm 12, which ignores npm_config_arch, npm_config_target_arch and
npm_config_nodedir ("Unknown env config", which it says will become an
error) and blocks install scripts outside allowScripts ("install scripts
blocked"). So nothing was compiled.
releases/compile-native-addons.mjs now runs node-gyp itself, using the
node-gyp that the install brings (pinned to 13.x, which finds Visual
Studio 2026), with --arch for the target CPU. win32 also passes the nodedir
that holds the fork's ia32 node.lib. It does this for every copy of each
named package that has a binding.gyp and no win32-<arch> prebuild, before
prune-build-only-modules removes node-gyp. It fails if node-gyp fails or
leaves no .node. Both jobs no longer set the npm_config_ variables.
Checked with the real node-gyp 13.0.2, argon2 0.44.0 and bcrypt 6.0.0
(with their win32 prebuilds removed, compiled for this machine): both
compiled, loaded, and hashed and verified a password.
tests/compileNativeAddons.test.cjs covers the plan, the node-gyp arguments
and every way the compile could quietly not happen.
tests/win32BundleBuild.test.cjs pins the step in both jobs and refuses
npm_config_arch/_target_arch/_nodedir in any workflow.
tests/releaseBundleCoverage.test.cjs used to require npm_config_arch: ia32,
and now requires the node-gyp cross-build, with the reason recorded in the
test.
Thanks to xet7 !
After the 12.32 UCS upgrade every LDAP login failed with "LDAP authentication
failed". The UCS compose (wekan/univention) kept the 6.09 setting
LDAP_GROUP_FILTER_ENABLE=true with no group id attribute and no group name.
6.09 read that as "in any group". WeKan now refuses an incomplete group
filter, as intended since #3700, so isUserInGroup refused everyone. The
compose is fixed in wekan/univention: the filter is off, because UCS grants
access through (wekanActivated=TRUE), and the group id attribute is cn, so
Domain Admins become admins again.
releases/ucs-release.py now applies WeKan's own group-setting rule to the
compose before it prepares a portal upload. With the group filter or admin
sync on, it needs the three group lookup settings, and an enabled filter
needs a group name. A compose that would refuse every LDAP login is not
published. It refuses the 12.32 compose and passes the fixed one.
tests/ucsRelease.test.py covers both, plus the rule.
tests/ldapGroupFilterConfig.test.cjs shows that WeKan refuses the 6.09 UCS
settings and needs nothing missing with the fixed ones.
The UCS README describes the failure and gives a workaround for installs that
already upgraded: two appcenter/env/wekan UCR values and univention-app
reinitialize. It also explains the cross-origin error seen when WeKan is
opened through a host other than ROOT_URL.
Thanks to xet7 !
Record the 40 passing real browser checks across Nextcloud 32-35 and the mutual-TLS FRP subpath run, companion and release regressions, and the remaining Docker and ARM64 validation.
Thanks to xet7.
sharp makes the attachment thumbnails. Every bundle except amd64 is made
from the amd64 Linux bundle, and npm installed only @img/sharp-linux-x64
there. Reading the published v12.32 zips showed that every other bundle
carried that one package and nothing it could load: arm64, ppc64le, s390x,
riscv64, i386, armhf, armv7, armv6, mac-arm64 and mac-x64, as well as the
Docker images and snaps built from them. Thumbnails fell back to the
full-size image, and nothing failed.
releases/windows-native-packages.mjs becomes platform-native-packages.mjs,
<bundle> <win32|linux|darwin> <arch>. It removes the packages npm would not
install on that target (os, cpu, and on Linux libc), downloads
@img/sharp-<platform>-<arch> and its own libvips at the versions pinned,
checks each against the registry's sha512, and checks every native addon's
header for the target. sharp publishes nothing native for Linux i386 or
loong64, so there it uses @img/sharp-wasm32. That package is now a root
optional dependency in package.json, at sharp's own range. It was run
under Node 26 with no native package: it made a 512 px WebP thumbnail and
a GIF.
check-addon-arch.mjs now reads ELF (with byte order) and Mach-O (including
universal binaries) as well as PE, and follows npm's libc rule on Linux.
The script now runs in build-amd64, build-arm64 (in its arm64 container),
both macOS jobs, all three Windows jobs, and build-extra-arches through
install-node-for-arch.sh. There it runs with the target node's own
process.arch, so armv6/armv7/armhf are arm, ppc64le is ppc64 and i386 is
ia32. build-release-bundle.sh runs it for the local machine.
Against the published v12.32 zips, given their native addons, it added the
right sharp and libvips for all eleven Linux and macOS bundles, kept wasm32
for i386, and passed every bcrypt, argon2 and sharp binary. Checked the
other way, the s390x bundle was refused as ppc64.
tests/platformNativePackages.test.cjs covers the script with a mocked
registry (libvips, wasm fallback, wrong CPU, tampered tarball, path
traversal). tests/sharpEveryPlatform.test.cjs requires every job that makes
a bundle zip to run it for its own target, after the prune and before the
zip, and the wasm32 dependency to stay at sharp's version.
Thanks to xet7 !
Every Windows bundle is made from the amd64 Linux bundle, so sharp, which
makes the attachment thumbnails, had only @img/sharp-linux-x64 there. It
could never load on Windows, and every thumbnail fell back to the full-size
image. sharp publishes @img/sharp-win32-ia32, -x64 and -arm64 with the
libvips DLLs included, so nothing needs compiling.
releases/windows-native-packages.mjs puts in the package at the version
sharp pins, checks it against the registry's sha512, and removes the
packages whose os/cpu name another platform (sharp-linux-x64, its libvips,
@napi-rs/canvas-linux-x64-gnu). canvas is only pdfjs-dist's optional
dependency for rendering pages; WeKan only extracts text with pdfjs, and
there is no win32-ia32 canvas.
Running the addon check on the published v12.32 zips found a worse fault:
the win-arm64 zip has no arm64 argon2. argon2 0.44 publishes no win32-arm64
prebuild, and the job set no npm_config_arch, so node-gyp-build's install
test passed in the x64 runner node with the win32-x64 prebuild and compiled
nothing. accounts-password imports argon2 with no fallback. The rebuild
step now sets npm_config_arch=arm64, so argon2 is cross-compiled with
Visual Studio's ARM64 tools, which the runner image includes.
All three Windows jobs now run windows-native-packages.mjs and then
check-addon-arch.mjs for bcrypt, argon2 and @img/sharp-win32-<arch>, before
the zip is made. check-addon-arch accepts scoped package names. Against the
published v12.32 zips, win64 passes after the script, and win-arm64 still
fails on argon2, which the npm_config_arch fix addresses at build time.
tests/windowsNativePackages.test.cjs covers the script with a mocked
registry, including a tampered tarball and a path-traversal entry.
tests/win32BundleBuild.test.cjs pins the steps in all three jobs and the
target CPU of the cross-built ones.
Thanks to xet7 !
Adds an Upcoming section for it, and notes in the released v12.32 entry that its win32 bundle was not published after all, and where the fix is.
Thanks to xet7 !
v12.32 was the first release whose build-win32 got past the Node.js lookup,
and it died at the first step it had never run:
tar: D\:\a\\_temp/node-ia32: Cannot open: No such file or directory
RUNNER_TEMP is a Windows path, and GNU tar in Git Bash reads anything before
a colon as a remote host. The step now builds the nodedir on the POSIX form
of the path (cygpath -u), unpacks the headers by relative name inside it,
checks that include/node/node.h arrived, and gives node-gyp the Windows form
(cygpath -w).
The step after that had never run either. bcrypt and argon2 publish no
win32-ia32 prebuild, so they must be compiled for ia32, and accounts-password
imports both with no fallback. A cross-build that silently did nothing would
still zip and pass 7z t, and fail only at the first sign-in.
releases/check-addon-arch.mjs reads each shipped addon's PE header and fails
the job unless bcrypt and argon2 each have one for ia32. It skips packages
whose os/cpu name another platform, as npm does. On the published v12.31
win64 zip it passes as x64 and refuses the same zip as ia32.
tests/win32BundleBuild.test.cjs pins the new step, checks that no Windows
job in any workflow gives tar a RUNNER_TEMP or GITHUB_WORKSPACE path, and
tests the checker on synthetic PE and ELF files, positive and negative.
Thanks to xet7 !
Whitespace only: the Nextcloud entry's two paragraphs are rewrapped at 80 columns, with the same words in the same order, so tests/changelogFormat.test.cjs passes again.
Thanks to xet7 !
Shorten In short to three sentences covering all four topics, remove a UCS sentence pasted into the docs reorganization entry, describe the UCS topic as a whole, and wrap its long lines at 80 columns.
Thanks to xet7 !
The drop handler must call sortable('cancel'), which puts the dragged card
back at its source until Blaze re-renders the move. The drop preview from
b687f37272 fills the target slot during that gap, but the real card stayed
visible at the source, so the card was drawn in both lists. The reporter's
newest video shows that frame: card "2" already in the target list and still
in the source list with its old count, then the source catches up.
retireCardDropPreview() now also hides the node that sortable('cancel') put
back, with an inline style, which Blaze does not manage there:
- a move to another list keeps it hidden until Blaze removes it, even when
the new card renders a flush earlier;
- a reorder within one list shows only the preview until Blaze moves the
node beside it, instead of jumping the card back to its old place first;
- a rejected move, or one that never arrives within 10 seconds, shows the
card again where it is, so no card is left invisible.
tests/cardDropPreviewSource.test.cjs drives the helper on a minimal DOM: the
video's cross-list drop, a reorder, a drop in place, a rejected move, a
timeout, and a source copy that is never removed. It also checks that every
caller passes the node to hide. It fails on the video's frame without this
fix. 37-card-drag-sort.e2e.js now checks the source while the preview is
shown, and adds a reorder within one list.
Thanks to mimZD and xet7 !
Fixes#6430,
Run the temporary migration worker from the ordinary versioned application image. Prefer the target App Center compose images, then try the same upstream version on GHCR, Docker Hub and Quay before downtime. Reject inaccessible or incomplete images before portal upload. Cover UCS-only access, fallback, failure reporting, cleanup and migration integrity with regression tests.
Thanks to xet7 !
The autoupdate design pages move from docs/Design/Autoupdate to where their
platform lives: the per-OS pages to docs/Platforms/FOSS/OS/OS-future-alternatives,
Snap-Core and Snap-Ondra-Gantt to Container/Snap, UCS and Nextcloud to
Container/Docker, and Platforms.md to docs/Platforms. docs/Platforms/FOSS/Cloud
is split into Container/Docker (Helm, OpenShift, CloudFoundry) and
FOSS/CloudFlare; Friend and UCS move under Container/Docker; Leo moves to
Features/ImportExport/Leo.
Every link to a moved page is updated: relative links in the docs, including
the moved pages' own links to the rest of the tree, full docs/ paths in the
workflows, tests and comments, and the Upcoming CHANGELOG entry. Released and
archived CHANGELOG sections are records and keep their original links.
tests/docsLinksResolve.test.cjs passes.
Thanks to xet7 !
wekan/node-patches publishes node-win32.exe with its checksum in
node-win32.sha256sum (without .exe), as the line `<hash> *node-win32.exe`.
releases/resolve-node-source.sh looked only for node-win32.exe.sha256sum and an
unmarked name, so it answered node_found=false and build-win32 skipped itself
every release, although both binaries were published. The resolver now also
tries the sidecar without .exe and accepts sha256sum's `*` binary-mode marker,
and strips CR. Verified against v26.11.1: win32 resolves, and the downloaded
node-win32.exe matches the published SHA256. tests/releaseNodeSources.test.cjs
runs the matcher on real sidecar lines, both positive and negative.
Thanks to xet7 !
docs/Design/Autoupdate/Platforms.md now lists every binary that the newest
wekan/node-patches (v26.11.1, 16 targets) and wekan/FerretDB (v1.92.0, 25
targets) releases publish. Its "what is built" table matches what v12.30 and
v12.31 shipped and ghcr.io/wekan/wekan:v12.30 contains: armv7, armv6, win64,
win-arm64, mac-x64 and mac-arm64 bundles are added, riscv64's Node.js comes from
node-patches for v26.11.1, and loong64 is skipped. The doc also explains why
win32 is not built: the resolver looks for node-win32.exe.sha256sum, but
node-patches publishes node-win32.sha256sum. FreeBSD amd64 is listed as having
both binaries now.
Thanks to xet7 !
docs/Design/Autoupdate/Platforms.md opens with the open source platforms that
WeKan is not on yet, checked in each catalog on 2026-10-11, and what is still
missing for each. ZimaOS/CasaOS, TrueNAS, Coolify, Dokploy and the AUR get
the files their catalogs take and a README with the pull request each
expects, under docs/Platforms/. Each was tested with that catalog's own tools
in .tools/ where they run without the platform: CasaOS's validator and store
builder, the TrueNAS library render, Dokploy's three validators, and Arch's
makepkg, whose package started WeKan. tests/platformTemplates.test.cjs keeps
the image tag and settings the same across them.
Thanks to xet7 !
sandstorm.js was imported only by server/imports.js since mainModule turned
eager loading off, so its browser code never ran in a grain: the grain URL and
title did not follow the board, the Powerbox picker could not open, and the
isSandstorm helper was undefined, so email, password, logout and avatar
settings that Sandstorm handles showed anyway. The server half - which loads
the Cap'n Proto bridge client and node:net - is now server/sandstorm.js, and
the client imports the shared sandstorm.js. Its shell listener ignores
messages that are not its replies instead of throwing on them.
The Powerbox button is back in the members widget, for people who may invite.
It passes the open board, and the claimed person is invited to it through
inviteUserToBoard, with the member picker's own permission checks; #2405
removed the old button because the identity it claimed was never attached to
a board.
Thanks to xet7 !
start.js killed the please-wait bridge and then loaded main.js, but
Meteor 3 awaits every Meteor.startup hook before webapp calls listen, so
nothing answered on port 4000 for all of WeKan's startup. The
sandstorm-http-bridge got connection-refused and the browser showed
NS_ERROR_NET_EMPTY_RESPONSE. The bridge now keeps the port until Meteor
binds it: the first listen on the app port waits for the bridge process
to exit and then binds, and logs "WeKan is listening on port 4000".
Thanks to xet7 !
When there is horizontal room, the source list and the parts list flow
into as many columns as fit (a column width, not a count), so WeKan,
Trello, Jira and the rest are all on screen at once instead of below
the fold. A phone keeps one column.
Thanks to xet7 !
v12.29's build-win64 failed before building anything: community.chocolatey.org
answered 504 and `choco install -y 7zip` could not resolve 7zip.install.
windows-latest already has 7-Zip, so build-win64, build-win-arm64 and
build-win32 use it, and fall back to Chocolatey with five retries only on an
image without it.
Thanks to xet7 !
2026-10-11 16:05:42 +03:00
3356 changed files with 575572 additions and 463328 deletions
if curl -fsS -o /dev/null "http://127.0.0.1:8080/sign-in"; then echo "WeKan answered on port 8080 in Alpine."; kill $pid; exit 0; fi
kill -0 $pid 2>/dev/null || break
sleep 2
done
kill $pid 2>/dev/null || true
echo "::error::the musl AppImage did not answer on port 8080 in Alpine; its log follows"
tail -n 100 smoke.log
exit 1'
exit 0
fi
# A CHECK THAT CANNOT RUN IS NOT A FAILED CHECK. Probe the INNER Node,
# not the AppImage runtime: on the arm64 runner the armhf AppImage
# runtime itself executes, while its bundled hard-float Node cannot
@@ -394,7 +486,7 @@ jobs:
# AppDir is the exact tree just packed, so this also avoids extracting
# the large image merely to make the capability decision.
if ! LD_LIBRARY_PATH="$PWD/AppDir/usr/lib/wekan/bundle/node-runtime${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" AppDir/usr/lib/wekan/bundle/node --version >/dev/null 2>&1; then
echo "::warning::this runner cannot execute the bundled ${{ matrix.appimage_arch }} Node binary, so the AppImage was packed but not started. It is uploaded unchecked."
echo "::warning::this runner cannot execute the bundled ${MATRIX_APPIMAGE_ARCH} Node binary, so the AppImage was packed but not started. It is uploaded unchecked."
echo "::notice::No base bundle (wekan-${version}-{amd64,arm64}.zip) is on ${{ needs.release.outputs.tag }} yet, so there is nothing to wrap - skipping. release-all.yml builds the AppImages once the base bundles are published."
echo "::notice::No base bundle (wekan-${version}-{amd64,arm64}.zip) is on ${NEEDS_RELEASE_TAG} yet, so there is nothing to wrap - skipping. release-all.yml builds the AppImages once the base bundles are published."
exit 0
fi
if [ "${{ needs.build.result }}" = "cancelled" ]; then
if [ "${NEEDS_BUILD_RESULT}" = "cancelled" ]; then
echo "::warning::The run was cancelled before any AppImage finished building; nothing was attached."
- name:Check DOCKERHUB_AUTH is configured and works
env:
DOCKERHUB_AUTH:${{ secrets.DOCKERHUB_AUTH }}
INPUT_IMAGE:${{ inputs.image }}
run:|
set -uo pipefail
if [ -z "${DOCKERHUB_AUTH:-}" ]; then
@@ -79,16 +80,19 @@ jobs:
user="${creds%%:*}"
token="${creds#*:}"
if ! printf '%s' "$token" | docker login docker.io -u "$user" --password-stdin; then
echo "::error::docker-variant: DOCKERHUB_AUTH was REJECTED by docker.io (login failed as user '$user'). The token is wrong, expired, or lacks push rights for wekanteam/${{ inputs.image }}."
echo "::error::docker-variant: DOCKERHUB_AUTH was REJECTED by docker.io (login failed as user '$user'). The token is wrong, expired, or lacks push rights for wekanteam/${INPUT_IMAGE}."
exit 1
fi
echo "docker-variant: DOCKERHUB_AUTH works for docker.io (user '$user')."
- name:Work out which version to publish
id:v
env:
INPUT_VERSION:${{ inputs.version }}
INPUT_IMAGE:${{ inputs.image }}
run:|
set -euo pipefail
version="${{ inputs.version }}"
version="${INPUT_VERSION}"
if [ -z "$version" ]; then
# The newest release of wekan/wekan, asked of GitHub rather than guessed.
echo "::error::v${{ steps.plan.outputs.version }} still lacks ${{ steps.plan.outputs.manual }} after recovery. Inspect the matching full-release run; older unnamed runs need their failed jobs retried manually."
echo "::error::v${STEP_PLAN_VERSION} still lacks ${STEP_PLAN_MANUAL} after recovery. Inspect the matching full-release run; older unnamed runs need their failed jobs retried manually."
exit 1
# The six architectures nobody else builds for, repacked from the PUBLISHED
# amd64 bundle - which is what makes this possible without a Meteor build.
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
@@ -112,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).
@@ -35,7 +35,7 @@ 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.
@@ -340,7 +340,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
@@ -373,7 +373,7 @@ directly after the merge.
| --- | --- | --- | --- |
| `.tools/FerretDB` | wekan/FerretDB | `main-v1` | the FerretDB v1 fork WeKan ships as its default database |
| `.tools/node-patches` | wekan/node-patches | `main` | patches to upstream Node.js; builds the `node-<platform>` binaries the bundles, the Docker image and the snap embed |
| `.tools/mongo-tools-patches` | wekan/mongo-tools-patches | `main` | patches to the MongoDB Database Tools; builds `<tool>-<arch>` |
| `.tools/mongo-tools-patches` | wekan/mongo-tools-patches | `main` | patches to the MongoDB Database Tools; builds `mongo-tools-<arch>`, all eight tools as one program |
| `.tools/TSC` | Secretchronicles/TSC | `devel` | the game xet7 also maintains — same author and maintainer mode, its own GNU ChangeLog and release process |
| `.tools/sandstorm` | sandstorm-io/sandstorm | — | upstream Sandstorm, when present — somebody else's project, cloned for reference |
@@ -414,7 +414,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 +426,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
@@ -35,7 +35,7 @@ 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.
@@ -340,7 +340,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
@@ -373,7 +373,7 @@ directly after the merge.
| --- | --- | --- | --- |
| `.tools/FerretDB` | wekan/FerretDB | `main-v1` | the FerretDB v1 fork WeKan ships as its default database |
| `.tools/node-patches` | wekan/node-patches | `main` | patches to upstream Node.js; builds the `node-<platform>` binaries the bundles, the Docker image and the snap embed |
| `.tools/mongo-tools-patches` | wekan/mongo-tools-patches | `main` | patches to the MongoDB Database Tools; builds `<tool>-<arch>` |
| `.tools/mongo-tools-patches` | wekan/mongo-tools-patches | `main` | patches to the MongoDB Database Tools; builds `mongo-tools-<arch>`, all eight tools as one program |
| `.tools/TSC` | Secretchronicles/TSC | `devel` | the game xet7 also maintains — same author and maintainer mode, its own GNU ChangeLog and release process |
| `.tools/sandstorm` | sandstorm-io/sandstorm | — | upstream Sandstorm, when present — somebody else's project, cloned for reference |
@@ -414,7 +414,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 +426,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
if ! NODE_META="$(bash /tmp/resolve-node-source.sh "${WEKAN_ARCH}""${NODE_MAJOR}")";then
echo"No Node.js ${NODE_MAJOR}.x for ${WEKAN_ARCH} at nodejs.org, unofficial-builds.nodejs.org or wekan/node-patches, so this image cannot be built for it. Build node-${WEKAN_ARCH} in wekan/node-patches, or drop ${TARGETARCH} from the image's platform list." >&2
exit1
fi
eval"${NODE_META}"
echo"Node.js ${node_full} for ${WEKAN_ARCH}: ${node_from} (${node_url})"
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|||"
@@ -2653,6 +2697,7 @@ RELEASE_SCRIPTS=( "Release|Release All: audit, commit, push and build with Actio
"Bundles|Fetch the built bundle from the ppc64le build host|releases/up-o.sh|WeKan version, e.g. 10.50||"
"Bundles|Fetch the built bundle from the s390x build host|releases/up-s.sh|WeKan version, e.g. 10.50||"
"Bundles|Upload the Windows bundle to the download server|releases/up-w.sh|WeKan version, e.g. 10.50||"
"Docker images|Test Nextcloud 32-35 with native local services|tests/nextcloud/run.sh|Nextcloud majors (default 32 33 34 35)|linux|nextcloud-tests"
"Docker images|Build the WeKan Docker image|releases/docker-build.sh|||"
"Docker images|Create the multi-platform buildx builder|releases/docker-build-deps.sh|||"
"Docker images|Publish a variant image (wekan-gantt-gpl / wekan-ondra)|releases/docker-publish-variant.sh|Image and version, e.g. wekan-gantt-gpl 10.50||"
"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>/"\
"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>/)";;
"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>/)"\
"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"\
@@ -2969,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
@@ -3331,6 +3377,14 @@ for _once in 1; do
break
;;
"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>/")
# releases/live-tests/run.mjs: every live test there; each skips a
# service it has no login for. Human-run only - they write to the
# services (scratch items they remove), so no automatic run calls them.
node "$WEKAN_DIR/releases/live-tests/run.mjs"
break
;;
"Mirror GitHub to active mirrors: code, issues, PR conversations, releases and assets")
mirror_forge
break
@@ -3360,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")
File diff suppressed because it is too large.
Load diff
Loaded 100 of 3356 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.