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 !
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.
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.
The attachment REST API carried every file as base64 inside JSON: an upload
gathered the whole body into a string, decoded it and stored it, and a
download read the whole file and encoded it - the file and its base64 copy
in memory at once, up to the API limits. Beside that form, each route now
takes the file itself: an upload whose body is the file (any Content-Type
but JSON, its card or board in the query) streams into storage, and
?raw=1 on a download streams the file out, served as a download that
cannot run in WeKan's origin. Both make the same checks first - one helper
now holds the card upload checks for both forms - and the API limits are
checked against Content-Length before reading and counted as the bytes flow.
api.py uses the raw form for its four attachment and background commands,
streaming from and to disk. The DDP methods stay base64 by their nature;
docs/API/Attachments.md says to use the raw routes for large files.
Removed from TODO Later.
Thanks to markusst1982 and xet7 !
Every login that chooses between a popup and a redirect opened a popup by
default. Google's sign-in pages, and other identity providers', can send
Cross-Origin-Opener-Policy, which makes Meteor's popup look closed at
once: the login was tried before the provider answered, and the user
was back on the sign-in page with no error. Popups are also blocked in
iframes and on some phones. The full-page redirect has no popup to lose.
- OAUTH2_LOGIN_STYLE (OAuth2/OIDC): redirect unless it says popup, in the
server, the Admin Panel catalog and the samples.
- OAUTH_PROVIDERS_LOGIN_STYLE (Google, GitHub, Facebook, X, Meteor
Developer, Weibo, Meetup): redirect by default, in the code, Dockerfile,
snap, Sandstorm package and samples.
- SAML: both identity-provider profiles log in by redirect;
SAML_LOGIN_FLOW=popup still chooses the popup, and the client no longer
falls back to a popup while the configuration loads.
- CAS: redirects unless "popup": true. Its redirect could never finish:
nothing called Meteor.initCas, which the package now does at startup.
The sign-in page also passed username and password as loginWithCas's
options and callback, so its callback never ran, and the CAS redirect
page echoed the request address into HTML unescaped.
A redirect login's error now reaches the reloaded sign-in page, from
accounts-oauth's page-load hook and the SAML and CAS events, instead of
the page coming back silently.
Verified against a freshly built and prepared bundle with the local
identity-provider suite and no login style set: OIDC, SAML, CAS and all
seven Meteor providers log in by redirect, and each refusal is shown on
the sign-in page (31 of 31 with Chromium). Its LDAP wrong-password case
expected wording that changed in 679a8b3499 and is corrected.
Thanks to xet7 !
#2906 asked how to add and archive lists by script. Lists could be added,
edited and deleted through REST, but not archived or restored: the PUT edits
only live lists and has no archived field. POST
/api/boards/:boardId/lists/:listId/archive and .../unarchive now do what the
list menu does, for a user with write access to the board, answering 404
when the list is not in the expected state. docs/API/Lists.md shows both.
Thanks to stevekiss and xet7 !
Fixes#2906.
#4294 asked for four things. Several triggers and actions, rule variables,
renaming in place and rule import/export were built earlier; two parts were
still missing.
- "I want the assignee to be the creator of the card by default": rules had
member actions only. Add and Remove assignee actions take a username, a
{creator}/{assignees}/{members} token, the user who triggered the rule, or
"*" for every assignee, and write card.assignees. They are durable through
Sync like the member actions, with the joinAssignee/unjoinAssignee
activities the assignees hook writes.
- "give a Rule a default name": Add Rule with an empty title showed an
error, and rules.createRule stored the literal "Rule". The title is now
optional; the rule is named after its trigger and action ("When ...,
then ..."), and the rules list renames it in place.
Fixing this found that "Remove all members from the card" iterated the
card's assignees while removing from its members: an earlier fix believed
assignMember wrote assignees, but both definitions in models/cards.js write
members. It removed only members who were also assignees. Remove-all now
iterates card.members, in the ordinary and the durable path, and the guards
that pinned the wrong collection now read models/cards.js to decide.
New text is added in English, pending Transifex.
Thanks to xeruf and xet7 !
Fixes#4294.
Add September feature instructions, preserve guide forwarding links, and document backup coverage with descriptive screenshots from #6683.
Thanks to markusst1982 and xet7 !
Add optional checklist dueAt with the shared selected-calendar picker and badge. Return checklist and item deadlines from checklist and single-card GETs. Accept validated timezone-qualified ISO deadlines in checklist/item POST and PUT; null clears, omission preserves. Keep existing write permissions and ownership checks. Node regression and Chromium save/clear test pass.
Thanks to rmb82 and xet7 !
Fixes#6693,
GET /api/boards/:boardId/boardViewSettings answers which entries of the
Board View menu a board offers, which one it opens in, separately for a
public and a private board, and the menu order - in the normalised shape the
menu renders with: every view once with both showOnPublic and showOnPrivate
explicit, the two defaults resolved (missing or unknown reads as Swimlanes),
boardViewOrder made whole, and `keys` listing the known views. Board access.
PUT /api/boards/:boardId/boardViewSettings takes any subset of
boardViewSettings, defaultPublicBoardView, defaultPrivateBoardView and
boardViewOrder, through the same pure modifiers the popup's clicks apply
(showBoardViewModifier, defaultBoardViewModifier, normalizeBoardViewOrder),
now composed by boardViewSettingsRequest() in models/lib/boardViewSettings.js:
a default is always shown on its side, hiding a side's current default is a
400 (set another default first - or in the same request, since defaults are
applied before the show flags), an unknown view key anywhere in the body is a
400 and nothing of that request is written, and the order is normalised so the
menu lists every view exactly once. Board admin (or site admin), the same
check as cardFieldOrder. Both answer boardViewSettingsSnapshot().
The existing GET/PUT /api/boards/:boardId/cardSettings routes get the JSDoc
@operation blocks the OpenAPI generator reads (get_board_card_settings,
update_board_card_settings), so the hand-written copy in
openapi/extra_paths.yml - which now duplicated the operationId - is removed.
public/api/wekan.yml and wekan.html are regenerated with the release
workflow's own commands: 158 operations. docs/API/REST-API.md documents both
endpoints with curl examples, and docs/Features/Board/Board-View-Settings.md
links to them.
tests/restApiNewFeatureRoutes.test.cjs pins the four routes to their method,
path, @operation and auth check, that the PUT writes only the helper's $set,
the snapshot's shape, the request helper's positive cases (setting a default
shows it, one request may move the default and hide the old one, the order is
normalised, the input board is not mutated) and negatives (unknown view key in
the order, the settings or a default; hiding a side's default; malformed
bodies; a partly-invalid body applies nothing), and that the generated and the
committed spec carry the four operations exactly once with the old
edit_board_card_settings gone.
Thanks to xet7 !
Every feature of the recent releases that had a UI but no endpoint, on the
same server-side code the UI uses:
- DELETE /api/boards/:boardId/attachments/:attachmentId soft-deletes,
POST .../attachments/:attachmentId/restore restores, and
GET /api/boards/:boardId/attachments/deleted lists what is restorable -
all through the attachments.softDelete / attachments.restore methods the
card and the card history use, run as the request's user. No hard delete.
- GET /api/admin/problems (the Problems status overview plus the new-problem
count per stream), GET /api/admin/problems/:stream (one page, with
limit/skip/search) and POST /api/admin/problems/:stream/acknowledge,
through the admin-only eventLog* methods the Admin Panel pages call.
- GET /api/admin/oauth-providers, PUT /api/admin/oauth-providers/:providerKey
and PUT /api/admin/passwordless, through saveOauthProviderSettings,
savePasswordlessSettings and getOauthProviderConfigSources. A provider's
secret is reported only as { source, hasValue }, never its value.
- GET/PUT /api/boards/:boardId/cardFieldOrder, normalised with the same
applyCardFieldOrder() the card renders with.
- PUT /api/boards/:boardId/rules/:ruleId accepts `enabled` to pause and
resume a rule, and GET reports it.
The OpenAPI generator is fixed on the way: server/models/boards.js has had a
bare `catch {` since v11.67, the esprima parser cannot read that, and a parse
failure was silently skipped - so public/api/wekan.yml has shipped WITHOUT
the whole Boards API since then. It now downlevels `catch {` and
`for await (`, warns when a file cannot be parsed, and no longer emits an
empty sub-schema for a primitive array-element marker such as
`wipLimitGroups.$.listIds.$`, which made the spec unparseable YAML.
public/api/wekan.yml and wekan.html are regenerated with the release
workflow's own commands: 156 operations, up from 122.
tests/restApiNewFeatureRoutes.test.cjs pins every new route to its method,
path, @operation block and authentication check, that the OAuth endpoints
never mention a secret outside the input whitelist, the generator's two
fixes, that the generated and the committed spec carry the Boards API and the
new operations, and - as the negative sweep - that no route in models/ or
server/models/ lacks an authentication check.
Thanks to xet7 !
There was no comparison of the roles anywhere. docs/Features/Members/Members.md
listed three of them - Admin, Normal, Comment only - in one line each, and there
are NINE: board admin, normal, no comments, comment only, worker, read only, and
an assigned-only variant of normal, comment-only and read-only. docs/API/Role.md
shows how to set each flag over the API without saying what any of them does.
docs/Features/Members/Roles.md is the table: for every role, which cards it sees,
whether it may comment, create or edit cards, move cards, edit lists and
swimlanes, and change the board's settings and members. It also says WHERE each
column comes from, because the answer is only two helpers -
allowIsBoardMemberWithWriteAccess() and allowIsBoardMemberCommentOnly() in
server/lib/utils.js - plus isBoardAdmin() and, for visibility, the assigned-only
scope in the card publications. The table is what the SERVER allows, because the
server is the authority and the UI can only hide buttons.
VALIDATED FROM THE CODE, and three things do not match their own names. They are
recorded as gaps, not fixed here, because each needs a decision about which side
is wrong:
* "Comment only, assigned" (isCommentAssignedOnly) has FULL WRITE ACCESS.
Nothing outside the card publications ever reads that flag - it is not in
allowIsBoardMemberWithWriteAccess(), where isCommentOnly is - so the member
may create and edit cards, lists and checklists. The role is in practice
"Normal, but only sees the cards assigned to me", which is what
isNormalAssignedOnly already means.
* "No comments" (isNoComments) cannot write ANYTHING. That helper excludes it,
so the role blocks editing as well as commenting - while the schema describes
it as "is the member not allowed to make comments" and the UI's
canModifyCard() does not exclude it, so the member is offered the edit
affordances and the server refuses the write. The name and the schema comment
both point at the helper being the wrong side.
* The write helper does not exempt board admins. Every has*() helper on the
board requires isAdmin: false, so a flag on an admin is ignored, but that one
reads the raw flags. Not reachable from the Web UI, which writes all eight
flags at once and never leaves two set - but reachable over the REST API,
which takes them individually.
A fourth section lists where the UI is more generous than the server, i.e. the
buttons that do nothing: canModifyCard() does not exclude isNoComments, and
canModifyBoard() excludes neither isNoComments nor isWorker. None of these is a
security hole - the server rules hold - but a button that silently fails reads as
a bug to whoever clicks it.
tests/boardRoles.test.cjs keeps the page honest rather than trusting it, which is
the point: a permissions table that quietly goes stale is worse than none, because
it is what an admin decides who to trust with. It parses the table and checks that
every role memberRole() can return has a row and every row names a flag it really
reads, that the "create / edit" and "comment" columns match the flag lists in the
two server helpers, that the "which cards they see" column matches the
assigned-only scope, that only the board-admin row may change settings, that each
gap the table marks is still real AND still explained - so fixing one has to
update the page with it - and that the page is linked from both Members.md and
the API role page.
Thanks to xet7 !
Sandstorm username races.
- #2345: the Card Title Filter is now SAVED by every board trigger
builder (a trigger doc missing the field could never satisfy the
matcher's $in - those rules fired for nothing), SHOWN in the rule
details, and ENFORCED for archive/restore triggers by resolving the
title from the card when the activity lacks it; legacy field-less
triggers keep matching; the filter popup prefills its current value.
- #2674: rules created via POST/PUT /api/boards/:boardId/rules never
fired - missing matching fields are now normalized to '*' wildcards
and trigger/action types validated; the addMember/removeMember rule
actions no longer crash on unresolvable usernames or member-less
cards and their writes are awaited. New docs/API/Rules.md (endpoints,
matching fields, wildcard semantics, the exact two-rule
add-on-move-to/remove-on-move-from workflow), linked from REST-API.md;
api.py help now lists the rule commands with the same example.
- #574: Sandstorm unique-username generation probes case-insensitively
(anchored, regex-escaped) and retries the next number when a
concurrent insert wins the unique-index race, instead of aborting the
login hook with E11000.
Tests: tests/rulesCardTitleFilter.test.cjs (12),
tests/rulesApiTriggerNormalize.test.cjs (19),
tests/sandstormUsername.test.cjs (11) - positive and negative cases.
Thanks to InfoSec812, sfahrenholz, mitar and xet7.
Many docs still used flat GitHub-wiki page-name links like [Backup](Backup),
[OAuth2](OAuth2) or [FAQ](FAQ) that only resolved on the old wiki; in the docs/
hierarchy they were broken. Rewrite them to correct relative paths of the
existing pages (e.g. ](Backup) -> ](Backup.md), ](OAuth2) -> ](../Login/OAuth2.md),
](Sandstorm) -> ](../Platforms/FOSS/Sandstorm/Sandstorm.md)).
Mapping: page name -> docs/<...>/<Name>.md by filename, with explicit handling
for renamed pages (Caddy/Nginx-Webserver-Config, Wekan-Markdown, the Sandstorm
grain export page, Uberspace) and the REST-API-<X> family -> docs/API/<X>.md.
Ambiguous names resolved by preferring a same-directory file or the primary page.
Anchors and markdown titles preserved. 280 links across the docs tree.
Out of scope (left as-is): source-code references (models/*.js etc.) and
[Link](url) placeholders, which are not docs pages.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The GitHub wiki is disabled and its content was migrated into docs/, so every
https://github.com/wekan/wekan/wiki/... link now redirects to the repo home and
is broken. Rewrite all internal links in docs/ to relative paths:
- wiki/<Page> -> relative path to the corresponding docs/ file (renamed pages
mapped explicitly: Caddy-Webserver-Config->Webserver/Caddy.md, Wekan-Markdown
->Features/Markdown/Markdown.md, REST-API-Role->API/Role.md, etc.)
- wiki home -> docs/README.md
- blob|tree|raw/main|master/<path> -> relative path (anchors and #Lxx line
ranges preserved; bare URLs wrapped as [name](path) so they stay clickable)
Kept absolute (intentionally not flattened to the current ref):
- ref-pinned links (edge, devel, version tags, commit hashes)
- links whose target no longer exists in the tree
wiki/issues -> the GitHub issues tracker URL (external). 436 links across 113
files; verified no wiki links remain and no broken relative links introduced.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add REST endpoints for board domain sharing and global-admin settings, document
exporting large boards without base64 attachments, and document template
drag-to-share drop-target gating plus Propagate Members To Boards behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>