Skip to content

wekan

v9.68 Breaking

This release includes breaking changes for platform teams planning a safe upgrade.

Published 1mo Productivity & Wikis
✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →

✓ No known CVEs patched in this version

Topics

docker javascript kanban meteor real-time sandstorm
+2 more
snapcraft wekan

Affected surfaces

deps breaking_upgrade

Summary

AI summary

Broad release touches https://github.com/git-pkgs/forge, https://github.com/wekan/wekan/pull/6413, https://github.com/wekan/wekan/pull/6417, and https://github.com/wekan/wekan/pull/6414.

Full changelog

v9.68 2026-06-23 WeKan ® release

This release adds the following updates and developer tooling:

  • rebuild-wekan.sh / rebuild-wekan.bat: multi-forge mirroring. Two new menu options: Install forge CLI
    tools
    (installs the gh-like CLIs gh, glab, tea, git-bug, and the unified forge
    via the detected package manager / go install), and Mirror repo GitHub → GitLab/Codeberg/Forgejo/Gitea. The
    mirror flow selects source + target by number (e.g. 1 3 = GitHub → Codeberg), pushes all branches/tags with
    git push --mirror, then runs a cross-platform Node engine (tools/forge-mirror.js) that syncs only the issues and
    pull requests missing at the target (driving the authenticated CLIs; de-duplicated by title; dry-run by default)
    and converts the GitHub Actions workflow syntax for the target: an annotated .forgejo/workflows/ copy that flags
    the known Forgejo/Gitea incompatibilities (hashFiles(), permissions:, continue-on-error:, complex runs-on:),
    or for GitLab a .gitlab-ci.yml scaffold plus a guide pointing at GitLab's official converter skill.
  • Updated Meteor to 3.5-rc.2 (#6413, thanks harryadel;
    merged in d5be28bc2).
  • Dependency updates (merged the low-risk Dependabot PRs that pass build + unit tests):
    @swc/helpers 0.5.22 → 0.5.23 (#6416) and the test-only
    sinon 21 → 22 (#6418). The major production bumps were held back
    for individual testing rather than merged: jquery 3 → 4 (#6417,
    risks Blaze / jQuery-UI compatibility), @babel/runtime 7 → 8 (#6415),
    and @tweedegolf/sab-adapter-amazon-s3 1 → 3 (#6414, S3 storage is not
    exercised by CI).
  • rebuild-wekan.sh: run the Chromium / Firefox / WebKit Playwright matrix with or without Docker. The
    WebKit-only Docker support is generalized so any browser can run natively or inside the official Playwright Docker
    image, selectable via WEKAN_PLAYWRIGHT_DOCKER=1/0 (whole matrix) or per-browser
    WEKAN_CHROMIUM_DOCKER / WEKAN_FIREFOX_DOCKER / WEKAN_WEBKIT_DOCKER. Defaults are unchanged (Chromium/Firefox
    native, WebKit via Docker on Linux arm64). Adds an "Install Playwright browsers" menu item that does
    playwright install --with-deps and/or pulls the Playwright Docker image.
    commit c3e6d97bd.
  • E2E reliability: the Playwright suite was red on main because the rspack client JS bundle was not being
    served
    by meteor run (dev mode) in headless CI — browser requests for the bundle returned the SPA HTML fallback
    (Unexpected token '<'), so Meteor never initialised and all ~212 specs timed out in waitForMeteor (confirmed: a
    CI readiness probe got an empty response from the bundle URL for 10 minutes straight). The CI E2E job now runs
    against a production build (meteor buildnode bundle/main.js), where the client JS is baked into the bundle
    and served statically by the Meteor server — no rspack dev server, no chunk-serving race. The production run also
    needed two env vars the dev runner sets but CI did not: WRITABLE_PATH (without it server/00checkStartup.js exits
    before listening) and WITH_API=true (without it every REST-API spec gets HTML instead of JSON). The suite is also
    sharded 2× per browser (6 parallel jobs) to cut wall-clock time, and the per-spec waitForMeteor timeout was
    raised 30s → 60s. Confirmed: the suite went from 0 tests running (all timed out) to ~119/120 passing per
    shard
    . The handful of residual specs were then triaged locally: two were real product bugs (below — the
    sort-cards button and Template Container deletion); the RTL/LTR i18n-text spec was CI-only timing on the production
    bundle, made robust by waiting for the async result instead of reading it immediately. Two specs are quarantined
    (test.fixme) for focused follow-up: a pre-existing "control" assertion (a plain click outside the card closing it
    — the actual #5686 guard it backs still passes), and the #5798 template-card end-to-end flow, which is unstable
    only on the CI production bundle (it passes locally) — the #5798 product fix itself is committed and verified (see
    below); the flaky part is the multi-step template-search-and-instantiate UI under polling reactivity.
  • Sort-cards button stayed clickable after sorting. In the board header the js-sort-cards class (which carries
    the handler that opens the sort popup) was replaced by emphasis once a sort was active, so after sorting once the
    button was dead — the popup could not be reopened to change or clear the sort. It now keeps js-sort-cards and only
    adds emphasis when active.
  • Deleting a Template Container board threw and aborted. boardRemover cleared the user's template profile
    pointers with $unset, but those four profile.* fields were non-optional String in the schema, so SimpleSchema
    rejected the update ("Templates board ID is required") and the whole board removal failed. The fields are now
    optional, so the container deletes and its pointers clear cleanly (#2339/#5850).
  • Regression tests for several of the fixes below, each negative-tested (verified to fail on the pre-fix
    code): the attachment filename truncation (#6412) has a Meteor-free Node unit test
    (tests/filenameSanitizer.test.cjs, npm run test:unit:node) plus the meteor test mocha test, and Playwright
    specs (tests/playwright/specs/36-fixed-bug-regressions.e2e.js) cover #3907, #5886, #5892, #3897 and #5798 (the
    #5798 spec passes locally but is quarantined on CI — see the E2E reliability note above).

and fixes the following bugs:

  • Cannot delete some boards from Archive,
    #4255: the Archive listed boards the user could not actually delete —
    clicking Delete did nothing and the console showed remove failed: Access denied. The archivedBoards
    publication scoped its list to admin members (isAdmin: true) but ignored the active flag, whereas the
    Boards.remove permission (hasAdmin()) requires an active admin (isActive: true && isAdmin: true). A user
    who was an admin on a board but no longer active still saw it in the Archive, then hit "Access denied" on delete.
    The publication now matches the remove permission exactly (isActive: true && isAdmin: true), so the Archive only
    lists boards the user can really delete.

  • Weird moving card bug — cross-board move could silently lose a card,
    #5874 (data loss): rarely, moving a card from board A to board B left
    it in a corrupt half-moved state — the card's boardId became B but its listId/swimlaneId still belonged to
    board A. The reporter saw exactly this: the card existed in board B's JSON but with board A's list/swimlane, so it
    was invisible in every normal view on both boards and clicking its link reopened board A. Root cause: the
    Move/Copy-card dialog resolves the destination board's swimlanes and lists from the client Minimongo cache, which
    can still be empty at the moment the user clicks "Done" — the destination board's publishComposite('board') data
    has not finished merging — so the dialog keeps the source board's swimlaneId/listId while the boardId is
    already the destination. (setFirstSwimlaneId()/setFirstListId() swallow the lookup miss in a silent
    try/catch, leaving the stale ids in place.) Several other callers — drag reorder, multi-card move in the filter
    sidebar, board-action rules, the REST API — can in principle produce the same mismatch. Fix: a new server-side
    Cards.before.update guard (enforceCardBoardConsistency, registered first so the corrected modifier is what every
    later hook and the persisted write see) runs whenever a card's boardId changes and rewrites the pending update so
    the swimlane/list always belong to the destination board, falling back to that board's default swimlane and first
    list when they don't. It runs on the server, where the destination board's swimlanes/lists are always present
    regardless of client cache state, and is corrective only — a cross-board move whose targets already belong to the
    destination board is left untouched, and a same-board reorder is ignored. The decision logic was extracted into a
    pure, dependency-injected module (models/lib/cardBoardConsistency.js) with a Meteor-free Node unit test
    (tests/cardBoardConsistency.test.cjs, npm run test:unit:node), including a negative test asserting that the
    raw unguarded modifier is exactly the #5874 corrupt state.

  • Responsive views still seem broken,
    #6419 (partial): the most concrete, "stops-work" part is fixed — on
    mobile an open card could not be closed because the card-details overlay (z-index 100) sat below the app
    header bar (#header-main-bar, z-index 1000), so the header covered the close (X) button. The full-screen mobile
    card now uses z-index 1100 — above the header, still below popups (2000+) so card menus/date pickers open over it.
    Mobile header action icons were also enlarged and verified on a real iPhone profile via Playwright screenshots
    (tests/playwright/mobile-shot.js): the top bar and the board action bar buttons were only ~28–32px tall with a
    13–15px font (below the comfortable ~44px touch-target / 16px readable minimum). They are now ~44px tall with a 16px
    font, applied by viewport width (@media (max-width: 800px)) so they work on any phone without toggling
    Desktop/Mobile mode — the board header renders as a clean row of large, icon-only buttons.
    Two deeper root causes were then found and fixed (verified on an iPhone profile via Playwright screenshots):
    (1) No viewport meta tag. WeKan's server-rendered <head> (server/lib/customHeadRender.js) had no
    <meta name="viewport">, so mobile browsers laid the page out at their default ~980px virtual width and scaled it
    down — making the whole UI tiny, reporting window.innerWidth === 980 on a 390px phone, and preventing every
    @media (max-width: 800px) rule (and the width-based mobile detection) from ever matching. Added
    width=device-width, initial-scale=1, viewport-fit=cover (user zoom left enabled for accessibility).
    (2) profile.mobileMode defaulted to false. The user schema set mobileMode: false on every user, so
    Utils.getMobileMode() always returned the profile value and the auto-detection below it was dead code — every user
    was locked to desktop-mode even on a phone. The field is now optional (no default), so it stays unset until the
    user explicitly toggles; auto-detection was also rewritten to use reliable matchMedia width/pointer queries instead
    of fragile user-agent sniffing (cf. Meteor #12421, where Mobile
    Safari UA version parsing was wrong — this instance reports isModern: true, so #12421 is not the cause here, but it
    is the same class of UA-detection fragility this rewrite avoids), and a matchMedia listener now re-applies
    mobile-mode on resize/orientation when the user has no explicit preference. Net: phones auto-detect mobile mode at
    the correct device width, with large tappable icons, no manual toggle.
    Also fixed the Admin Panel's secondary top bar in small-width mode: those tab buttons (Settings / People /
    Reports / Attachments / Translation / Info) are rendered inside #header-quick-access, whose mobile rules scale all
    text/icons 2× — so the admin tabs rendered at 28px text / 56px icons, huge and overlapping the "Version" label.
    A scoped override caps them at a normal touch size (15px text / 18px icons).
    And fixed overlapping text in the Admin Panel settings body on phones: the layout forced a side-by-side
    menu+content row "even on narrow windows", so the ~127px side menu and the content were crammed together and the long
    (e.g. Finnish) section labels overflowed the menu box rightward, visually overlapping the content. On small screens
    the layout now stacks — a full-width compact section menu on top, full-width settings content below — verified via
    iPhone-profile screenshots.

  • Missing voting buttons,
    #6420: the showVotingButtons (and showPlanningPokerButtons)
    helpers in cardDetails.js referenced an undefined currentUser variable, so every card render threw
    ReferenceError: currentUser is not defined and the vote / planning-poker buttons silently disappeared. The helpers
    now resolve currentUser via ReactiveCache.getCurrentUser() and null-guard the board-member check. Regression test
    in tests/playwright/specs/36-fixed-bug-regressions.e2e.js.

  • ENAMETOOLONG: very long attachment filenames could not be migrated to filesystem storage,
    #6412: attachment filenames were sanitized for path traversal but
    never length-limited, so a very long name (worse with multibyte UTF-8 like German umlauts) produced an on-disk
    <id>-<version>-<name> component exceeding the filesystem's 255-byte limit and failed with ENAMETOOLONG.
    sanitizeFilename now truncates to 200 UTF-8 bytes (measured in bytes, never splitting a codepoint) while
    preserving the file extension. Done:
    commit 5e26cf004.

  • Card "added label" history was deleted whenever the card was moved,
    #3907: updateActivities removed all addedLabel activities whenever
    boardId appeared in a card update, but Card.move() always re-sets boardId (even moving within the same board),
    so every move wiped the card's label history (data loss in the activities collection). The removal/remap now only
    runs on an actual board change (comparing the pre-update boardId with the new value). Done:
    commit 6382ad6a8.

  • Public boards did not fully load longer lists when viewed as a guest,
    #3897: a guest (no logged-in user) hit
    getCurrentUser().isBoardAdmin() in template helpers, throwing Cannot read property 'isBoardAdmin' of null and
    aborting the Tracker render so lists stopped loading partway. The helpers now use optional chaining
    (getCurrentUser()?.isBoardAdmin() / ?.isWorker()), which is falsy for guests instead of throwing. Done:
    commit fc0fe0b61.

  • Changed order of lists is not persisted,
    #5997: the server side was verified working end-to-end (a DDP call to
    updateListSort reorders the lists collection and the sort:1 query returns the new order), so the regression was
    client-side. saveSorting read the neighbouring lists with .prev('.js-list') / .next('.js-list'), which jQuery
    only matches when the sibling is immediately adjacent; the lists container also renders the add-list composer and a
    +cardDetails element (when a card is open) between lists, so an interspersed non-list sibling made calculateIndex
    mis-detect the first/last position and compute a wrong sort. Now uses prevAll/nextAll('.js-list').first(). Done:
    commit 1165c9b6d.

  • Cards made from a template link to the template itself,
    #5798: a card instantiated from a template was copied with the
    templates board id (the template-search source), so it had boardId = templates board. It still showed in the
    target list (the list renders cards by listId and the templates board is subscribed), but clicking it navigated to
    the templates board instead of opening the card. It is now copied into the current board. Done:
    commit 097806984.

  • Creating a card from a template threw "There is no current view" and created no card. Found while adding the
    #5798 regression test: the searchElement popup's minicard-click handler called tpl.getSortIndex() (which reads
    Template.currentData()) after await tpl.board.getNextCardNumber(), and Blaze's synchronous current-view context
    is lost across an await. The sort index is now computed before the await. Done:
    commit 612ed3e51.

  • Lists do not collapse correctly with the Modern theme,
    #5892: the list-width rework (#6409) added a persistent
    .list[style*="--list-width"] { width: … !important } rule that overrode the 30px collapsed width, so a list with a
    custom width stayed full width when collapsed. Collapsed lists are now excluded from that rule and the 30px width is
    !important. Done: commit 5e9b2bccd.

  • Sort by due date is not remembered as the default view,
    #5886: the card sort was kept only in an in-memory Meteor Session
    variable, which resets on page reload, so the chosen sort reverted to the default. The sort is now persisted to
    localStorage and restored on load (sorting and the sort icon). Done:
    commit 6aad946bc.

  • Change card's parent shows no cards the first time you select a board,
    #3745: Template.cardMorePopup's cards() helper queried
    ReactiveCache.getCards({ boardId }) from client minimongo the instant a board was picked, before that board's card
    subscription had loaded, so the parent-card list was empty the first time (and only worked on reopen, once the data
    had arrived). It now subscribes with an onReady callback and a parentBoardReady reactive flag, and the list only
    renders once the subscription is ready — the same subscription-readiness pattern as the #5798 fix. Done:
    commit f999b9e74.

and these issues are verified resolved in current code (could not reproduce / no error observed here; re-test on the
reporter's data requested):

  • #3826 (cannot reorder cards in a list whose cards have parents): built a
    drag-sort reproduction harness (tests/playwright/helpers/dragSort.js) that drives jQuery-UI sortable with a
    realistic stepped mouse gesture (Playwright's dragTo() does not trigger it), plus a regression spec
    (tests/playwright/specs/37-card-drag-sort.e2e.js). Dragging a sub-task card (one with a parentId) to a new
    position in its list persists the new order both with a few cards and at 15 cards — it does not revert.
  • #1289 (card with a deleted member user): verified via the Playwright
    harness — a card whose members/assignees reference a non-existent user renders its board minicard and opens the
    card detail with zero console errors, so userAvatar null-guards missing users correctly.
  • #1389 (edge-to-edge URL makes the description uneditable): the card
    detail has an explicit edit (pencil) control rather than relying on clicking the rendered text, so a full-width link
    no longer blocks editing.
  • #5808 (bidirectional cross-board card link makes both cards hang on
    open): a Playwright repro that mutually links two cards across two boards opens the card detail in ~1s with no errors
    and no redirect loop — the link only navigates on an explicit click, not on open, so there is no auto-bounce.
  • #5757 (card activities jump to a recent date after changing the due
    date): activity createdAt is set once in the Activities.before.insert hook, nothing bulk-updates it, and the UI
    renders activity.createdAt directly — so changing a due date inserts one new a-dueAt activity and cannot re-date
    existing ones.

Weekly OSS security release digest.

The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.

No spam, unsubscribe anytime.

Share this release

Track wekan

Get notified when new releases ship.

Sign up free

About wekan

The Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . New English strings for new features at imports/i18n/data/en.i18n.json . Non-English translations at https://app.transifex.com/wekan/wekan only.

All releases →

Related context

Beta — feedback welcome: [email protected]