Commit Graph
65 Commits
Author SHA1 Message Date
Lauri Ojansivu fae1ccd176 FerretDB high CPU: run FerretDB standalone (no OpLog) by default; enable the replica set / OpLog only when wekan-ferretdb-oplog=true.
The 10.16-10.21 reports of the ferretdb process pegging ~2 cores for a long time
while node sat idle are the signature of an OpLog tail: Meteor holds a tailable
cursor on local.oplog.rs and FerretDB re-scans it server-side. wekan-control
already defaults to polling-only (it clears MONGO_OPLOG_URL), but ferretdb-control
passed --repl-set-name unconditionally, so FerretDB ALWAYS maintained the capped
OpLog (a second write per mutation, a once-a-minute cleanup, and a tailable cursor
for any stray MONGO_OPLOG_URL to spin on) even when WeKan would not tail it.

Gate --repl-set-name on WEKAN_FERRETDB_OPLOG (default false), matching
wekan-control: in the default polling-only mode FerretDB now runs STANDALONE, with
no OpLog at all; the replica set / OpLog is created only when the admin opts in with
wekan-ferretdb-oplog=true. Opting in is now also cheaper on the FerretDB fork side
(the {ts: {$gt}} tail filter is pushed down and the OpLog Timestamp indexed).

Docs: docs/Features/Admin-Panel/Problems/Migrations.md (FerretDB reactivity mode).
Test: tests/ferretdbOplogGating.test.cjs.

Thanks to xet7 !
2026-07-22 16:51:09 +03:00
Lauri Ojansivu c4a76e0e3d Docs: Admin Panel / Problems — README overview + Snap commands + Repairs / Migrations / Login-Problems pages.
docs/Features/Admin-Panel/Problems/README.md describes what Problems offers and
links the detailed pages (existing CPU/RAM/Disk/Recovery plus the new Repairs,
Migrations, Login-Problems and Snap pages). Snap.md documents `snap run
wekan.problems` and its sub-commands (migrations | login | broken-cards | cpu).
Repairs.md / Migrations.md cover the shared board data-repairs and the MongoDB <->
SQLite migration, when they run (board open, server startup, during migration) and
the progress dashboard. Login-Problems.md explains the "Must be logged in" /
"Loading, please wait" causes and fixes, tying them to a saturated ferretdb.

Thanks to xet7 !
2026-07-22 16:22:39 +03:00
Lauri Ojansivu 8e4d57c21d Show a recovery maintenance page on all FerretDB v1 platforms
During a #6492 data recovery, users should never see a bare connection error.
Two layers now cover the whole window on every FerretDB v1 launch path (snap,
bundled release, Docker):

- In-app spinner (already present): the server clears the RECOVERY_IN_PROGRESS
  marker only after a real health probe, so the full-screen overlay behaves
  identically everywhere.
- Static bridge page (new, cross-platform): for the brief window while a
  just-restored FerretDB comes back up and before Meteor can serve the client,
  the launch scripts serve a tiny standalone "recovering your data" page (HTTP
  503) on the web port. The snap reuses wekan-maintenance-page.mjs with a
  recovery wording; release/Docker serve the portable recovery-bridge.mjs. The
  bridge is time-bounded (WEKAN_RECOVERY_BRIDGE_SECONDS, default 20s) so it can
  never block WeKan from starting, hands straight over to the in-app spinner, and
  is skipped if its page file or the marker is absent.

The launch scripts do NOT delete the marker; only the server does, after
verifying the database serves reads. Tests enforce the bridge is bounded, is
guarded by the page file, and that no script deletes the marker.

Thanks to bluetopaz1204, mueschel and xet7 !

Fixes #6492,
2026-07-21 10:06:41 +03:00
Lauri Ojansivu 63c77a27f4 Automatic recovery/remediation for the SQLite text data + Admin Panel Recovery report
Completes the #6492 data-safety system on the WeKan side (FerretDB detection/repair/cap
landed in the fork):

- Decision brain: models/lib/recoveryPlan.js decideRecovery() picks the least-invasive
  recovery when the database is KNOWN corrupt (latest good backup -> previous backup ->
  re-migrate from MongoDB -> manual). It never acts on a healthy/unknown database and
  never restores a known-bad backup.
- Startup restore/re-migrate: every FerretDB launch path (snap, bundled release, Docker)
  can restore the text data BEFORE FerretDB opens the files, on request
  (WEKAN_FORCE_RESTORE env or a RESTORE_REQUESTED marker). It copies a known-good backup
  INTO the live database (dropping stale WAL side-files); backups are never deleted and
  the main wekan.sqlite is only overwritten, never removed. remigrate is delegated to
  migration-control. Attachments/avatars on the filesystem are untouched.
- Recovery events: the scripts append JSON lines to recovery-events.jsonl; the server
  (server/recovery.js) imports them into the recoveryEvents collection on startup, plus
  an admin-only recordRecoveryEvent method.
- Admin Panel / Problems / Recovery: a new admin-only report
  (server/publications/recoveryReport.js, wired in adminReports.js/.jade + i18n) lists
  the remediation history newest-first, readies up front and fetches directly (no hang).
- Docs: docs/Features/Admin-Panel/Problems/Recovery.md.

Tests (wired into npm run test:unit:node), verified by mirror since there is no Node
runtime here to execute them:
  - recoveryPlan.test.cjs (decision logic, positive + negatives)
  - recoveryEventsJsonl.test.cjs (parser: skips junk, normalizes severity, never throws)
  - recoveryReportQuery.test.cjs (search selector escapes regex metacharacters)
  - recoveryReportWiring.test.cjs (report wired; publication/count/method admin-gated)
  - ferretdbTextDataBackup.test.cjs extended: restore copies from a backup, is
    request-gated, and NEVER deletes the live text data or a backup copy

Thanks to bluetopaz1204, mueschel and xet7 !

Fixes #6492,
2026-07-21 10:06:40 +03:00
Lauri Ojansivu 3aefa94a66 Board Statistics view (Tilastot): full-width board view, replaces Status popup
Move the board Status from a Board Settings sidebar popup into a
full-width board VIEW alongside Swimlanes / Lists / Calendar / Gantt /
Table in the board-view switcher. Named 'Statistics' (Finnish
'Tilastot'), translated to major languages (rest fall back to English).

New statsView template/js/css rendered from boardBody (isViewStats),
selectable via the switcher (js-open-stats-view -> board-view-stats,
added to the profile.boardView schema allowedValues). Reuses the server
boardStatus method so counts are accurate in lazy mode; shows the same
counts + time-spent summary as before. Text is selectable with mouse
(drag) and touch (long-press): the view stops pointer/touch events from
reaching the canvas drag-to-scroll so native selection + copy work.

Removed the old boardStatusPopup (template/handler/helpers/css) and its
Board menu item. Added a Playwright test for the view. Design doc
updated.
2026-07-20 14:47:32 +03:00
Lauri Ojansivu 54cc0ec614 docs: organize docs/Features into categories and update all links
Move the flat feature docs and feature subdirectories under category
parents: Admin-Panel/ (+ Problems/), Board/, Cards/, Editor/, Reports/,
Automation/, Lists/, Troubleshooting/. Every relative markdown link in
docs/ (and the docs GitHub URLs in CHANGELOG.md and the code-span path
in Undo.md) is recomputed so it still resolves to the same target after
the move.

Done with a scripted move + relative-link rewriter that verifies link
integrity: all 160 rewritten links resolve, and a full scan confirms the
reorg introduces zero new broken docs-internal links (the pre-existing
53 unrelated to the move are unchanged).
2026-07-20 13:37:44 +03:00
Lauri Ojansivu abc0f180fa Board Settings: add read-only Status popup (mode, counts, time-spent summary)
New 'Board status' item in the board menu opens a larger, mouse-
selectable popup showing this board's card-loading mode (lazy vs all,
and whether chosen automatically), counts (swimlanes, lists, cards,
archived cards, labels, members, custom fields) and a time-spent summary
in the style of the task time reports (total hours, cards with time,
overtime cards).

Counts come from a server boardStatus(boardId) method so they are
accurate even in lazy mode, where minimongo only holds the visible card
window; the time summary fetches only cards with spentTime > 0 so it
stays cheap on big boards. New i18n keys translated to major languages
(rest fall back to English). Design doc at
docs/Features/Board/Sidebar/Status.md.
2026-07-20 13:28:56 +03:00
Lauri Ojansivu f306061273 docs: Admin Panel / Problems monitors under Admin-Panel/Problems/ + RAM & Disk designs
Move CPU-usage design under docs/Features/Admin-Panel/Problems/ and add
RAM-usage and Disk-usage design docs (siblings of CPU-usage): both log
how much RAM+swap / disk is used and record only the START and END of
each sustained high-usage period to Admin Panel / Problems, mirroring the
CPU-usage monitor's shape (hysteresis tracker, event stream, env vars).
Link them from the Admin-Panel index and update the now-automatic card
loading description. Update the CHANGELOG link to the moved CPU-usage.
2026-07-20 13:21:02 +03:00
Lauri Ojansivu 7707736f96 CPU usage: adaptive feedback loop, FerretDB self-regulation, robust asks, i18n
Complete the high-CPU → FerretDB-slow-down → measure → report loop, and make it
robust when WeKan itself is CPU-starved or FerretDB is unresponsive.

- Adaptive feedback (server/lib/cpuMonitor.js): while CPU stays high WeKan now
  INCREASES (doubles) the delay FerretDB adds between operations, up to a cap,
  until system CPU drops below a headroom target (WEKAN_CPU_TARGET_PERCENT,
  default 70) — enough CPU for other processes — then holds. Each escalation, the
  headroom result, and the "even max delay not enough (load is elsewhere)" case
  are logged.
- FerretDB self-regulates too: WeKan reads the throttle response's autoSlowDownMs,
  hostCpuPercent and operationsSummary (what FerretDB has been doing) and logs
  them — so the feature works even if WeKan is too starved to drive the loop.
- No flooding / no hammering (server/lib/ferretdbGovernor.js): every call has a
  timeout (WEKAN_FERRETDB_TIMEOUT_MS); WeKan talks to FerretDB at most once per
  WEKAN_FERRETDB_ASK_INTERVAL_MS (30s); a non-answer starts a cooldown
  (WEKAN_FERRETDB_COOLDOWN_MS) instead of retrying. WeKan records WHEN FerretDB
  became unresponsive and, on recovery, logs the full start -> end span plus
  FerretDB's status and operations summary. "command not found" (plain MongoDB)
  is still treated as permanently unsupported.
- i18n: cpuReportTitle ("CPU usage") translated to all 154 languages.

Guard tests (tests/cpuMonitorWiring.test.cjs) extended for escalation, rate-limit,
timeout/cooldown, outage span, and FerretDB self-regulation/summary. Docs
(docs/Features/CPU-usage/CPU-usage.md) and CHANGELOG updated.
2026-07-20 04:34:38 +03:00
Lauri Ojansivu 8d2fa1e530 Use FerretDB's general throttle command name (was wekanThrottle)
FerretDB de-branded its custom command to the general name `throttle` (it is a
general-purpose MongoDB replacement, usable by any MongoDB-wire client, not only
WeKan). Update WeKan's caller to match: server/lib/ferretdbGovernor.js now sends
{ throttle: 1, ... } and the comments/guard test refer to the `throttle` command.
No behaviour change. WeKan-side names (ferretdbGovernor, slowDownFerretDb) are
WeKan's own and unchanged.
2026-07-20 03:37:21 +03:00
Lauri Ojansivu ee0aa29a17 CPU usage: on high CPU, ask FerretDB what it is doing and to slow down
FerretDB is the other big CPU user on the same host. When WeKan's CPU monitor
detects a sustained high-CPU period it now asks FerretDB (via the custom
wekanThrottle command added to the bundled wekan/FerretDB v1 fork) what it is
doing and to slow down, and writes it to Admin Panel / Problems / CPU usage.

- server/lib/ferretdbGovernor.js: runs the wekanThrottle command over the raw
  Mongo driver. slowDownFerretDb(slowDownMs, durationMs) asks FerretDB to pause a
  few ms before each command for a short, self-expiring window and returns its
  activity (commandsProcessed); resumeFerretDb() turns it off. Best-effort — on
  plain MongoDB or an older FerretDB the command fails once and is never retried.
- server/lib/cpuMonitor.js: on 'start' asks FerretDB to slow down and logs a row
  with FerretDB's reported activity and what it was asked; renews the
  self-expiring throttle each sample while CPU stays high; on 'end' asks FerretDB
  to resume and notes it. Tunable via WEKAN_FERRETDB_SLOWDOWN_MS.

Guard tests (cpuMonitorWiring) extended for the FerretDB governor. CHANGELOG +
docs/Features/CPU-usage/CPU-usage.md updated.
2026-07-20 03:17:54 +03:00
Lauri Ojansivu 82cc884f03 CPU usage report: log automatic mitigation taken and whether it helped
When CPU is high, the report now also records what automatic mitigation was
taken and whether it noticeably lowered CPU:

- Once the governor actually pauses a governed operation during a high-CPU
  period, write ONE 'rate-limited' row: slowing down "<activity>" — pausing
  200ms between steps to yield the CPU (also lowers FerretDB query load), with
  the CPU% at slow-down start.
- Track the lowest CPU% seen after slowing down; the 'end' row reports the
  effect: "slowed down <activity> (paused N times, Xs total). After slowing
  down, CPU went A% -> B% — noticeably lower (about -Z points)" (or "not
  noticeably lower"). When nothing was running to slow down, it says so.

Still at most three rows per episode (start, mitigation, end). The effect is a
correlation over the sampling window, reported as observed.

Also rename the design doc to docs/Features/CPU-usage/CPU-usage.md (CHANGELOG
link updated). Guard test extended for the mitigation logging.
2026-07-20 03:11:07 +03:00
Lauri Ojansivu 1c636f09b3 CPU-usage monitor + governor + Admin Panel / Problems / CPU usage report
WeKan and FerretDB on one machine can drive CPU high enough to starve other
software. Add a monitor, a governor, and a report. Design: docs/Features/CPU/CPU.md.

Monitor (server/lib/cpuMonitor.js): samples system-wide CPU% from os.cpus()
idle/total deltas on an interval (so it includes WeKan, FerretDB and every other
process). A pure, hysteresis state machine (models/lib/cpuHighTracker.js) turns
the samples into ONLY the START and END of each sustained high-CPU period — two
rows per episode, never a flood. Enters high after 3 consecutive samples >= 85%,
leaves after 3 consecutive < 70%. Each row carries duration, peak, system load,
and what WeKan was doing (setActivity label). Recorded to a new 'cpu' event
stream (server/lib/cpuLog.js).

Governor: cpuMonitor exports isHighCpu() and pauseIfBusy(), so long batch
operations await a short pause and yield the CPU only while the system is busy
(no cost when idle). Wired into the existing-file extension corrector. Tunable
via WEKAN_CPU_* env vars.

Report: 'cpu' added to EVENT_STREAMS; reuses the generic eventStreamReport
(stream="cpu") with search + pagination; a "CPU usage" item is added to the
Admin Panel / Problems side menu (cpuReportTitle i18n).

Tests: cpuHighTracker unit (start/end, duration/peak, no-flap hysteresis;
positive + negative) and cpuMonitorWiring source guards. CHANGELOG + design doc.
2026-07-20 03:11:07 +03:00
Lauri Ojansivu fdb683892d Log every filename/content sanitization to Admin Panel / Problems
Whenever a filename or file requires sanitization — on upload, migration, the
existing-file corrector, or viewing/serving — record it to the Security event
stream (action:'sanitized'), the companion to the existing action:'blocked'
upload rejections. Each row carries:

- WHEN: the event timestamp.
- WHO: the original uploader (userId), shown as a new clickable username column
  in the Problems event report.
- FOR WHAT REASON: sanitizationReasons() — URL-encoded name, invisible
  characters, typosquatting (look-alike characters), the specific exploit KIND
  (JavaScript code, XML code, XML loop (billion laughs), server-side PHP/ASP,
  template injection, HTML code), wrong file type (.txt -> .png), filename too
  long, or empty-name-generated.
- THE FILENAME: "from" -> "to".
- WHERE: board > swimlane > list > card, plus the board's organization(s) and
  team(s) (server/lib/fileContext.js).

New: classifyExploitKinds() + sanitizationReasons() (isomorphic), the
server-only fileContext resolver, filenameSanitizeLog helpers, catalog keys
file.sanitize / file.content / file.malware, and the uploader column in the
event report. Wired at all fix points (attachments/avatars onAfterUpload,
moveToStorage, correctFileExtensions, httpStreamOutput). EICAR now uses the
file.malware key. Logging is best-effort and never breaks the upload/view path.

Tests: classifyExploitKinds + sanitizationReasons unit tests (positive +
negative) and logging-wiring source guards. CHANGELOG + design doc updated.
2026-07-20 03:11:07 +03:00
Lauri Ojansivu 81dbecfb87 Safe filename handling: display, upload, existing files, migrations
One unified filename subsystem, used everywhere a name is shown, uploaded,
served, or migrated. Design: docs/Features/Filename/Filename.md.

Display (imports/lib/fileNameDisplay.js, general cleanFileName + global
{{cleanFilename}}/{{downloadFilename}} helpers): URL-decode, normalize to
generally-used characters (NFKC + confusable-homoglyph folding so typosquatting
"payp<Cyrillic-a>l.exe" shows as "paypal.exe", only in a Latin-majority base so
genuine non-Latin names are preserved), remove invisible/control/bidi chars,
strip HTML/JS/XML/template markup. Removed the earlier invisible-char filter
button, warning triangle, inline descriptions, legend and server invisibleOnly
filter.

Upload (attachments + avatars, all backends): reject exploit-looking filenames
and EICAR virus test files; strip invisible chars; correct the extension to the
real detected type (double-click opens the right app); type-based name when
empty; cap length to a portable max (30, Amiga OS FFS) preserving the extension;
sanitize SVG JS / XML-loop content at the staging location before promoting to
final storage and delete the temp file after; enough-disk-space check with
small-RAM streaming and stop-and-remove-partial on any write error.

Existing files: general storage-agnostic detector streams a small header to
WRITABLE_PATH/files/temp, runs `file`, deletes the temp file; admin-only bounded
batch extension corrector; serve-time on-the-fly exploit sanitizer wired into
httpStreamOutput (sniffs the start, rewrites only dangerous markup, streams the
rest unchanged).

Migrations (moveToStorage/copyFile): sanitize content, finalize the name with
the same general function (detected type, corrected extension, folded
homoglyphs, capped length), save it at the destination, and number
same-name/different-content collisions (document.pdf, document-1.pdf, ...).

Tests: rewrote fileNameDisplay.test.cjs; added uploadFileName, diskSpace,
fileTypeCorrection and fileHardeningGuards unit tests (positive + negative);
updated Playwright spec 41 to the clean-name model. CHANGELOG + design doc.
2026-07-20 03:11:07 +03:00
Lauri Ojansivu 46924985f8 Soft delete for lists: delete = mark, restorable + undoable (#1023).
Deleting a list no longer destroys it. The list and its cards are marked
(deletedAt/deletedBy/deleteBatchId), hidden from the board, recorded as a
reversible delete in history, and restorable via lists.restore or Ctrl+Z.
This is the first slice of "no permanent delete in ordinary use": physical
removal is limited to GDPR/account erasure and a Global-Admin purge gated by
a new Admin Panel / Features / Delete "Enable permanent delete" toggle
(off by default).

- models/lib/softDelete.js: pure notDeleted/softDeleteSet/restoreModifier/
  canPurge helpers (+ tests/softDelete.test.cjs, wired into test:unit:node).
  softDeleteSet omits falsy actor/batch and restore uses $unset so no null is
  ever written to a Date/String schema field.
- enablePermanentDelete feature flag (models/lib/featureFlags.js) + Settings
  schema field.
- deletedAt/deletedBy/deleteBatchId schema fields on Lists and Cards
  (optional, no null default -> absent means live).
- board/swimlane list render paths filter deletedAt: null.
- server/models/lists.js: shared softRemoveList() + lists.softRemove /
  lists.restore / lists.purge (purge gated on canPurge = isAdmin && flag);
  client delete handler and REST DELETE now soft-remove.
- userPositionHistory undo()/redo() handle actionType delete/restore via a
  batch-consistent _applyDeleted() helper.

Design + planned rollout (other entities, Recycle Bin, admin Delete category
table with GDPR data classes) in docs/Features/Undo/Undo.md.
2026-07-17 20:20:06 +03:00
Lauri Ojansivu 58344db0d7 Undo docs: soft-delete design (delete = mark, never destroy).
Every user-facing delete becomes a soft delete (deletedAt/deletedBy), making
deletions restorable and undoable (#1023). No permanent delete in ordinary
use: physical removal is limited to GDPR/account erasure and an explicit
Global-Admin purge gated behind Admin Panel / Features / Delete ->
"Enable permanent delete for Global Admin" (off by default). Covers scope
(soft vs exempt collections), the softRemove/notDeleted mechanism, cascade
via deleteBatchId, archived-vs-deleted distinction, undo/History integration,
and migration steps.
2026-07-17 20:07:09 +03:00
Lauri Ojansivu 9be41ab34b Docs: unify undo/redo into one universal change-History (every change) with many scoped views.
Per request, the History design now records EVERY change a user makes (not just
card groups) in one append-only changeHistory store, and undo/redo becomes the
keyboard front-end to it (restore the current user's most recent change) rather
than a separate feature - superseding userPositionHistory.

Same table + restore is surfaced from many menus, each just a scope filter over
the one store: card group, whole card, Member settings (per-user), Board Settings
(per-board), Swimlane menu, List menu, and any future entity menu. Scopes nest
(card in list in swimlane in board) via id-column ORs. Added a scoped-views
section, the scope/scopeId page() params, per-scope permissions, and updated the
phasing (migrate position undo/redo onto changeHistory first). Undo.md now points
at History.md as the source of truth for the unified direction.

Thanks to xet7.
2026-07-17 19:27:00 +03:00
Lauri Ojansivu f3354f5cc8 Docs: undo/redo + high-impact confirmation feature doc.
docs/Features/Undo/Undo.md documents the #6478 feature: position-move undo/redo
(card/list/swimlane) via userPositionHistory with an undone/undoneAt redo stack,
undoLast/redoLast methods, Ctrl+Z/Ctrl+Y bindings, and the touch confirmation for
cross-swimlane list moves. Records the inert-recording bug (collection referenced
as an un-imported global), the write/read wiring, retention, v1 limitations
(positions only; content undo is the separate History feature) and an extension
checklist.

Thanks to xet7.
2026-07-17 19:17:10 +03:00
Lauri Ojansivu 3d1276fc1f Docs: design for per-group card History (view + restore).
Detailed design doc at docs/Features/History/History.md for the requested card
details 'History' feature: per-group history popup with a searchable, paginated
(current page only), per-contributor change table and restore.

Key point: WeKan's Activities log records what happened but NOT before/after
values, so restore needs a new append-only cardGroupHistory content-versioning
collection - the doc specifies the model, write/read (paginated+searchable+
per-user) methods, the 2-pane UI (History + contributor avatars | table with
select/change-type/content/datetime, search+pagination+restore bar), RTL
mirroring, restore semantics (re-logged to both source and restorer; history is
append-only), security, and a phased rollout starting with Description.

Thanks to xet7.
2026-07-17 19:14:40 +03:00
Lauri Ojansivu 592d803924 Notifications feature: drop GDPR-specific wording
Disabling activities/notifications/watch can serve any law or policy, not only
GDPR. Remove GDPR mentions from the code comments and the en.i18n.json toggle
descriptions (and the Admin Panel docs), keeping the neutral #5820 reference.
2026-07-14 14:22:58 +03:00
Lauri Ojansivu 247522484c Tests + docs for Admin Panel / Features Security and Notifications options
- server/lib/tests/importExportSecurity.tests.js: positive and negative unit
  tests for the anonymization helpers (map building, user-doc scrubbing, @mention
  rewriting that ignores unknown handles and email addresses, requestedBy/assignedBy
  identity replacement, and safe no-ops on empty input).
- docs/Features/Admin-Panel/Admin-Panel.md: new Features tab section documenting the
  Performance, Security (rich-text hardening + import/export privacy) and
  Notifications (GDPR) toggles.
2026-07-14 14:03:32 +03:00
Lauri Ojansivu 5ef2adf6fb rebuild-wekan.sh/.bat: Dev server options kill existing Meteor on the port; docs for new menu
Every "Dev server" menu option now stops any Meteor dev server already
listening on its port before starting a fresh one, so re-running a dev
option no longer fails because the port is taken. .sh adds a reusable
kill_meteor_on_port helper called by all six dev options; .bat funnels
them through :runlog, which calls a new :kill_meteor_on_port (netstat +
taskkill). Both wait for the port to free up and escalate to SIGKILL.

Also update the build-from-source docs (README.md, Build-from-source.md,
Build-and-Create-Pull-Request.md, Emoji.md, and the two Sandstorm
developer docs) to the new two-level menu (Setup -> Install dependencies,
Setup -> Build WeKan, Dev server -> localhost:3000), fix a stale
dev-server port (localhost:4000 -> 3000), and fix the README anchor link
to the renamed heading. Add a CHANGELOG entry.

Thanks to xet7 !
2026-07-11 11:11:22 +03:00
Lauri Ojansivu d5be28bc2e Merge pull request #6413 from harryadel/update-meteor-3.5-rc.2
Update Meteor to 3.5-rc.2
2026-06-22 11:32:10 +03:00
Lauri OjansivuandClaude Opus 4.8 7b66884ba3 docs: fix bare wiki-style links to relative paths of existing docs pages
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>
2026-06-21 21:03:27 +03:00
Harry Adel 83a105f12d Merge branch 'main' into update-meteor-3.5-rc.2 2026-06-21 20:49:16 +03:00
Lauri OjansivuandClaude Opus 4.8 b912ddb6f5 docs: convert internal wiki/repo links from https URLs to relative paths
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>
2026-06-21 20:43:36 +03:00
Harry Adel 15b2c7160b Update Meteor to 3.5-rc.2 2026-06-21 20:41:30 +03:00
Lauri OjansivuandClaude Opus 4.8 32abf5de40 Migrate LaTeX math from MathJax to Temml (native MathML)
Math rendering ($...$ inline, $$...$$ block) was silently dropped in
63ce45c53 during the Meteor 3 refactor; the markdown-it-mathjax3 dependency
was left orphaned in package.json.

Restore it using Temml, which converts LaTeX to native browser MathML, instead
of bundling all of MathJax (which also had a render-twice bug). Wired via the
markdown-it-math@6 no-default-renderer entrypoint, calling temml.renderToString
directly to avoid a top-level await that the Meteor/rspack bundler dislikes.

The emitted MathML is allow-listed in the markdown package's DOMPurify config
(tags + presentation attributes), and MathML elements are exempted from the
"delete element if it has a style attribute" rule so block formulas
(style="display:block math") are not silently removed; style/class/scripts and
annotation-xml stay blocked, so security posture is preserved.

Net dependency change: removes MathJax (9 packages), adds markdown-it-math and
temml (3 packages).

Docs: docs/Features/LaTeX.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 20:24:53 +03:00
Lauri OjansivuandClaude Opus 4.8 ff582697f3 Add auto-width back, scoped per-board or per-user (#6409)
Re-introduces the "fit lists to content" auto-width mode that the width rework
removed, now following the same Shared/Personal scope as fixed widths:

- Shared mode (allowsPersonalListWidth=false): auto-width is a per-board setting
  (board.autoWidth), the same for everyone; togglable by members with write
  access (server method setBoardAutoWidth, gated on board membership like
  applyListWidth).
- Personal mode: auto-width is per-user (the existing profile.autoWidthBoards).

- models/boards.js: add `autoWidth` field + getter/setter.
- server/models/users.js: add setBoardAutoWidth method.
- client list.js: effectiveAutoWidth(); autoWidth() helper; canResizeList()
  returns false in auto mode.
- list.jade: add `list-auto-width` class and omit `--list-width` when auto, so
  CSS fits the list to content and hides the resize handle.
- listHeader popup: re-add the Auto list width toggle (writes to board or
  profile depending on scope; shown only to those who may change it).
- wekanCreator: carry board.autoWidth through import.
- docs + CHANGELOG.

Verified live: shared auto-width applies to all members (class set, no
--list-width, handle hidden); personal auto-width is per-user (one member auto,
the other keeps a fixed width + handle).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:49:47 +03:00
Lauri OjansivuandClaude Opus 4.8 b477b1383d Rework list widths: single width, Shared/Personal board scope (#6409)
List widths were confusing (per-list "min width / max width / automatic") and
unreliable (drag changes lost on reload; the popup wrote the shared lists.width
while drag wrote per-user profile, and rendering read the per-user one as a
floating min/max). The width also reverted to `auto` on reload because the
render set min/max-width but never the `--list-width` CSS variable the styles
actually use (with !important).

Now a list has ONE width, driven by `--list-width`, that persists across
reloads, with a board setting choosing the scope:

- Shared (default): width stored in lists.width, same for everyone; only members
  with write access can change it (read-only/comment-only no longer see the
  resize handle). Board setting `allowsPersonalListWidth` (default false).
- Personal: each user keeps their own width (profile, or localStorage when not
  logged in), falling back to the shared width then the default (272).

Changes:
- models/boards.js: add `allowsPersonalListWidth` + getter/setter.
- server/models/users.js: applyListWidth sets only `width` (constraint ignored).
- client list.js: single effectiveListWidth precedence, mode-aware saveListWidth,
  resize handle gated on canResizeList; removed auto-width/min-max code.
- list.jade: render `--list-width` (the variable the CSS uses), show resize
  handle only when allowed.
- listHeader popup: single width value; removed min/max + auto-width toggle.
- sidebar: board "Personal list widths" toggle + i18n.
- Import/export: exporter already carries lists.width + the board setting;
  wekanCreator now preserves list width/color/collapsed and the board's
  list-width scope (it previously dropped them).
- docs/Features/Lists/Lists.md: document the model.

Verified live: shared width renders + persists on reload and is identical for
two members; personal mode gives per-user overrides with shared fallback;
drag-resize saves to lists.width in shared mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:40:51 +03:00
Lauri OjansivuandClaude Opus 4.8 d33d8370c0 Update docs for upcoming-release features
Document the new features from the Upcoming changelog: subtask status + subtask
creation/landing-list/view/circular fixes (Subtasks.md); org/team board-member
restriction, per-user announcement dismissal and per-board comment-editing
restriction (Admin-Panel.md, Members.md); due-date colour coding + due-date change
count (Due-Date.md); Calendar respecting the start day of week (Day-of-week-start.md);
drag-and-drop from search results (Drag-Drop.md). Comment replies/restriction and
ICS import already have dedicated docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 20:50:14 +03:00
Lauri OjansivuandClaude Opus 4.8 0a43d8ac3c Add #6323: import Google Calendar .ics files into board cards (MVP)
New dependency-free iCalendar parser (server/lib/icsImport.js) maps each VEVENT
to a card with startAt/dueAt so events show on Calendar/Gantt views, plus an
importIcsToBoard Meteor method. Import-only; two-way Google Calendar sync is not
included. Parser unit tested (folded lines, escaping, all-day vs date-time, etc.).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:40:13 +03:00
Lauri OjansivuandClaude Opus 4.8 bdb8e62546 Add #5907, #5906: threaded comment replies and admin comment-edit restriction
Comments gain an optional parentId with a Reply link and an 'in reply to' quote
(#5907, MVP single-level threading). New board setting restrictCommentEditing
(default off): when on, only a comment's author may edit/delete it, enforced
server-side via collection hooks (#5906). canEditComment decision unit tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:40:13 +03:00
Lauri OjansivuandClaude Opus 4.8 84678d3492 Docs: document board-domains & GlobalAdmin REST APIs, big-board export, and template drag-to-share / propagate-members (#5850).
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>
2026-06-19 11:37:44 +03:00
Lauri OjansivuandClaude Opus 4.8 6b093bd137 Docs: document new LDAP/OAuth2 env vars and template-sharing features (#4737, #4738, #4739, #4736, #5876, #5850).
Document LDAP background-sync user disabling and Organizations/Teams group sync,
OAuth2/OIDC auto-registration and admin groups, NOTIFY_ON_ASSIGN, and the Admin
Panel People Domains tab plus per-org/team toggles and domain board sharing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:35:29 +03:00
Lauri OjansivuandClaude Opus 4.8 8062ffc39f Fix broken popup Popup.open calls; finalize popup titles; Red Strings docs ASCII art
- bookmarks: Popup.open('bookmarksPopup') resolved to a non-existent
  'bookmarksPopupPopup' template (open() appends "Popup"). Fixed both calls to
  Popup.open('bookmarks') so the bookmarksPopup template + bookmarksPopup-title
  ("Starred boards") apply.
- Removed two dead handlers that opened non-existent templates with no
  triggering button: 'click .js-import' -> boardImportBoard (boardHeader.js x2,
  sidebar.js) and 'click .js-due-cards-view-change' -> globalSearchViewChange
  (globalSearch.js; the globalSearchHeaderBar template has no such button).
- Set the error popup titles to "List width error" / "Swimlane height error".
- docs/Features/RedStrings: added ASCII-art step-by-step for drawing a line by
  dragging the connect handle, and for changing a line's relation type/color/icon.

Verified: spec 27 passes 9/9 on chromium.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 02:25:37 +03:00
Lauri OjansivuandClaude Opus 4.8 cad7d77fce Red Strings: switch drag-to-connect to a per-card handle (no connect mode)
Replaces the modal "Connect" toggle with an always-available, non-modal UX so
cards stay clickable:
- Each minicard shows a small connect handle (right edge, on hover) when the
  dependency overlay is on and the user can edit the board. Drag it onto another
  card to create a dependency (dashed guide line follows the cursor).
- Connection lines are individually clickable (pointer-events:stroke) to open the
  edit/delete popup, while the overlay SVG stays click-through.
- Removed the board-header Connect toggle, the Session connect-mode state and the
  is-connecting overlay capture; drag is initiated from the handle (capture-phase
  mousedown so it beats the card drag-sort).

Updates the e2e test to drag the handle, docs to describe the no-mode UX, and
i18n (drag-to-connect, en/fi). Verified: spec 27 passes 9/9 on chromium.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 01:55:30 +03:00
Lauri OjansivuandClaude Opus 4.8 cc41308a3f Red Strings: drag-to-connect mode, on-board line editing, Miro import
Adds a piplanning.io / Kendis / Miro-style drag-to-connect UX:
- Board header "Connect" toggle (members with edit access) enters a transient
  connect mode (Session-based) and turns the overlay on.
- In connect mode the SVG overlay captures pointer events: drag from one card
  to another to create a dependency (a dashed guide line follows the cursor),
  and click a connection line to open a popup to change its relation
  type/color/icon or delete it.
- Out of connect mode the overlay stays non-interactive (cards clickable).

Import from other tools (best-effort): the Dependencies (JSON/SVG) importer now
also maps Miro REST API data (items + connectors -> lines, resolved to titles;
"block"/"fix" captions -> relation type). Kendis/piplanning.io have no public
file format — documented the generic { "lines": [...] } interchange they (and
GitHub/GitLab) can be converted to.

Also: shared DEPENDENCY_ICON_CHOICES in models/metadata/dependencies.js (used by
card detail + the new line editor), i18n (en/fi) for connect/done, CSS for
connect mode + clickable lines + dashed temp line, docs updated, and an e2e test
for drag-to-connect. Verified: spec 27 passes 9/9 on chromium.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 01:55:29 +03:00
Lauri OjansivuandClaude Opus 4.8 e82d4bb338 Docs: mark Collapsible Swimlanes (#2804) as implemented in Templates.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:38:09 +03:00
Lauri OjansivuandClaude Opus 4.8 492bc71c3c Docs: merge Shared Templates into Templates.md and refresh template status
- Move the Shared Templates (Admin view) content into docs/Features/Templates.md
  as a section and delete the separate SharedTemplates/ page; repoint the
  Features index, docs/README.md and the ChangeLog to the in-page anchor.
- Templates.md: relabel the working features as "Implemented and working"
  (per-user Card/List/Board templates + the admin Shared Templates view), and
  correct the roadmap list which still listed Shared templates (#2209) as a
  pending funded item although it is now implemented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:35:31 +03:00
Lauri OjansivuandClaude Opus 4.8 00046b893b Docs: add Shared Templates feature page; tidy Red Strings ChangeLog entry
- docs/Features/SharedTemplates/SharedTemplates.md: documents the admin
  "Shared templates" view (Admin Panel → People), grouping by Org/Team/Domain,
  the adminSharedTemplates method and the e2e coverage. Linked from the Features
  index, docs/README.md and Templates.md; ChangeLog entry links to it.
- CHANGELOG: reordered the Red Strings "Upcoming" entry so the feature
  description, REST API, import/export and preservation/tests read in order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:31:20 +03:00
Lauri OjansivuandClaude Opus 4.8 2a0d9cbf09 Red Strings: typed relations, color/icon, badge, filter, REST API, import/export, docs
Greatly expands the card dependency "Red Strings" feature (#3392).

Data model
- cardDependencies entries are now objects { cardId, type, color, icon }
  (models/metadata/dependencies.js defines the 5 relation types, defaults and a
  normalizer that upgrades legacy bare-string ids on read).

Relations + visualization
- Relation types: related-to, blocks, is-blocked-by, fixes, is-fixed-by. The
  type sets the SVG arrow direction (related-to is undirected); per-line color
  and per-color arrowhead markers; arrows drawn prerequisite-right -> dependent-left.
- Minicard badge (icon + color + count) for cards with dependencies.
- Board Filter sidebar: filter cards by dependency relation type.

Card detail UI
- Each dependency row has a relation-type dropdown, color picker, icon picker
  and remove; the add popup picks type + color then searches a card by title.

REST API + tooling
- server/models/dependencies.js: list/add/edit/remove endpoints (tag
  Dependencies) with OpenAPI annotations + board-level list endpoint.
- api.py: list/add/edit/remove dependency commands.

Import / export
- Board Settings -> Export: Dependencies as JSON and as round-trippable SVG
  (client/lib/exportDependencies.js).
- All Boards -> New -> Import -> Dependencies (JSON/SVG) into a chosen board,
  matching cards by id/number/title (importBoardDependencies method +
  client/lib/importDependencies.js).
- jiraCreator maps Jira issuelinks best-effort to dependency relations.

Preservation
- type/color/icon preserved through card/board copy and WeKan import/migrate;
  cross-board card move drops now-dangling dependencies.

Docs/tests/i18n
- docs/Features/RedStrings/RedStrings.md (+ Features index and docs/README links).
- i18n keys; CSS for the rows/badge/icon picker; expanded e2e spec 27.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:49:00 +03:00
Lauri Ojansivu f0bce67a3b More Import Export and Rules features.
Thanks to xet7 and Claude !
2026-06-15 04:07:33 +03:00
Lauri Ojansivu 83a0dd2565 - Reorganized and updated the documentation:
- Fixed 14 broken relative links in `docs/README.md` (verified with a link check).
  - Split the monolithic `docs/Features/Features.md` into focused topic pages under
    `docs/Features/` subdirectories: Boards, Lists, Cards, Members, Keyboard
    Shortcuts, Admin Panel, WIP Limits, Cleanup and Stats; `Features.md` is now an
    index linking to them, and `docs/README.md` links the new pages.
  - Added documentation for previously-undocumented features: Right-to-Left (RTL)
    UI, Stickers, Card Locations, Board Background Images, Attachments and File
    Storage, the card "complete" checkbox, and a new Accessibility feature page
    (covering both the Admin Panel / Settings / Accessibility info page and the
    built-in accessibility features above).
  - Updated outdated information based on the CHANGELOG: the Meteor/Node versions
    (now Meteor 3.x / Node.js 24.x) and the obsolete feature wishlist (Custom
    Fields, Subtasks, Swimlanes, Gantt, WIP limits, voting and templates are now
    implemented).

Thanks to xet7 and Claude !
2026-06-14 19:51:27 +03:00
Lauri Ojansivu f6d8befb56 Moved website to separate repo. Moved docs2 back to docs.
Thanks to xet7 !
2026-04-28 11:59:16 +03:00
Lauri Ojansivu 1c0d8e9964 Moved docs to docs2, and wekan.fi to docs, to be able publish with GitHub Pages.
Thanks to xet7 !
2026-04-16 14:36:10 +03:00
Lauri Ojansivu 7fb6e2a5d9 Added docs about If you get "Board not Found" after archieving Template Board.
Thanks to marcuskki and xet7 !

Related #4632
2026-03-13 20:53:17 +02:00
Lauri Ojansivu f34e4c0e36 Gantt chart view to one board view menu Swimlanes/Lists/Calendar/Gantt.
Thanks to xet7 !

Fixes #2870
2025-12-22 16:51:10 +02:00
Lauri Ojansivu 9a7fa25ca3 Added missing screenshots from wekan.github.io history to wekan/docs/Features/ . Thanks to xet7 2025-10-02 20:05:38 +03:00