Compare commits

...
162 Commits
Author SHA1 Message Date
github-actions[bot] 634e32620c Bump versions for v11.78 2026-09-14 11:32:07 +00:00
Lauri Ojansivu f40d29a6e5 Prepare v11.78 release 2026-09-14 14:30:32 +03:00
Lauri Ojansivu cc00713b2c Organize upcoming changes and refresh translation status according to changelog rules 2026-09-14 14:29:59 +03:00
Lauri Ojansivu b232abe1a0 Record dated Basque rule terminology repair and remaining grammar review 2026-09-14 14:27:51 +03:00
Lauri Ojansivu 2a22a42a96 Correct Basque rule archive and restoration terminology 2026-09-14 14:27:31 +03:00
Lauri Ojansivu a6da38e763 Document automatic Git menu merge and translation review progress 2026-09-14 14:26:42 +03:00
Lauri Ojansivu f23a18515a Automate orphan autostash recovery and merge origin in Git menu 2026-09-14 14:26:20 +03:00
Lauri Ojansivu ffde8e84ee Validate Basque rule action and date-field labels 2026-09-14 14:25:29 +03:00
Lauri Ojansivu 39f4f573d8 Document Git menu recovery in upcoming changelog 2026-09-14 14:24:29 +03:00
Lauri Ojansivu f665f7cdaf Guard Git menu operations and report interrupted pull cleanup failures 2026-09-14 14:24:18 +03:00
Lauri Ojansivu ec5a6464fc Merge remote-tracking branch 'origin/main' 2026-09-14 14:23:49 +03:00
Lauri Ojansivu 3ad3491f39 Update dated translation audit and upcoming review progress 2026-09-14 14:23:27 +03:00
Lauri Ojansivu 4c788beef0 Validate Basque date parent-task and standalone rule labels 2026-09-14 14:23:10 +03:00
Lauri Ojansivu 84514bb203 Record dated Basque impersonation translation review 2026-09-14 14:22:21 +03:00
Lauri Ojansivu f29c14def7 Validate Basque impersonation wording against dictionary and handler 2026-09-14 14:21:50 +03:00
Lauri Ojansivu 0443b18e38 Add Frappe responsiveness browser regression and upcoming notes 2026-09-14 14:21:21 +03:00
Lauri Ojansivu 670c203113 Stop Frappe Gantt chrome observer from rewriting identical labels 2026-09-14 14:20:56 +03:00
Lauri Ojansivu fb74726c9e Document full-width board views in upcoming changelog 2026-09-14 14:20:35 +03:00
Lauri Ojansivu fda197bb00 Use full available width for timeline and shared board report views 2026-09-14 14:20:23 +03:00
Lauri Ojansivu 74ecca7814 Record Basque date and webhook translation review progress 2026-09-14 14:19:59 +03:00
Lauri Ojansivu a076987c96 Validate Basque date activities upload outcomes and webhook labels 2026-09-14 14:17:24 +03:00
Lauri Ojansivu e35ba91dfd Update LDAP investigation and language picker changelog 2026-09-14 14:16:25 +03:00
Lauri Ojansivu dad65fb6c5 Verify LDAP issue 6692 through actual bind and user search methods
The latest 11.77 report still shows the undefined-index error fixed locally in 2c5cdd78a. Exercise service-account binding followed by user search with unset and configured filters, and prove failed binding cannot proceed to search. The separately reported oplog observer error is not established as the authentication cause.

Thanks to Nissulya and xet7 !

Fixes #6692,
2026-09-14 14:16:19 +03:00
Lauri Ojansivu c3ce54fa21 Show full-width language picker with separate language and region lines 2026-09-14 14:15:31 +03:00
Lauri Ojansivu 220a6beb69 Record Basque report and storage translation review progress 2026-09-14 14:14:23 +03:00
Lauri Ojansivu ea16ef95e4 Validate Basque team reports and storage migration messages 2026-09-14 14:13:58 +03:00
Lauri Ojansivu 1ee15661a3 Record Basque search terminology repair and review progress 2026-09-14 14:13:28 +03:00
Lauri Ojansivu 9910a2c138 Clarify Basque archived search status and validate operator instructions 2026-09-14 14:12:59 +03:00
Lauri Ojansivu de9da747a7 Record Basque rule and activity translation review progress 2026-09-14 14:12:19 +03:00
Lauri Ojansivu e34b55d698 Validate Basque rule fragments and activity settings 2026-09-14 14:11:54 +03:00
Lauri Ojansivu 251e352245 Record Basque archive translation fixes and review progress 2026-09-14 14:11:22 +03:00
Lauri Ojansivu 629577951a Clarify Basque archive activity terminology and validate instructions 2026-09-14 14:10:57 +03:00
Lauri Ojansivu f7a816eb22 Record Basque activity and Galician label review progress 2026-09-14 14:10:08 +03:00
Lauri Ojansivu 641062f8cd Validate Basque activity messages and Galician session labels 2026-09-14 14:09:46 +03:00
Lauri Ojansivu bceb89d928 Document activity placeholder repair and translation review progress 2026-09-14 14:09:02 +03:00
Lauri Ojansivu 888e0e6bcd Substitute activity message values through current translation API
Pass activity values as sprintf options and discard Spacebars helper metadata so card and sidebar feeds show actual titles and labels instead of literal percent placeholders. Preserve the security-aware viewer and sanitizing. Verify substitution using the real i18next formatter and register browser assertions for both feeds.
2026-09-14 14:08:37 +03:00
Lauri Ojansivu a5e111f910 Validate Galician repository and account translations 2026-09-14 14:07:04 +03:00
Lauri Ojansivu 30abbc9ae2 Record Galician scheduler translation review progress 2026-09-14 14:06:27 +03:00
Lauri Ojansivu d2780f0082 Validate Galician scheduler and migration threshold translations 2026-09-14 14:06:05 +03:00
Lauri Ojansivu 1ca33bf70f Record Galician subtask agreement and migration review progress 2026-09-14 14:05:25 +03:00
Lauri Ojansivu 01fa30d1c3 Fix Galician completed-subtask agreement and review migration steps 2026-09-14 14:04:56 +03:00
Lauri Ojansivu cfeeb65d46 Record Galician storage migration translation review progress 2026-09-14 14:04:10 +03:00
Lauri Ojansivu b71db72ab0 Validate Galician storage migrations and progress labels 2026-09-14 14:03:49 +03:00
Lauri Ojansivu 196b82d78c Record Galician S3 and migration translation review progress 2026-09-14 14:03:14 +03:00
Lauri Ojansivu 9683042c4e Validate Galician S3 settings and migration outcomes 2026-09-14 14:02:45 +03:00
Lauri Ojansivu 86dc493da9 Document historical mirror recovery in upcoming changelog 2026-09-14 14:02:10 +03:00
Lauri Ojansivu 44e3e1fd75 Recover unavailable mirror links from creation-date Wayback history
Bound attachment requests and defer long rate-limit cooldowns while preserving rate-limit state. Recover missing public links from the closest historical capture, retain provenance, and skip optional failures so remaining content synchronization continues.
2026-09-14 14:01:59 +03:00
Lauri Ojansivu f753dedd93 Record Galician migration and backup review progress 2026-09-14 14:00:13 +03:00
Lauri Ojansivu 4e3802901e Validate Galician migration retry and backup translations 2026-09-14 13:59:56 +03:00
Lauri Ojansivu 9e8d8da268 Document staged parallel mirror synchronization in upcoming changelog 2026-09-14 13:59:20 +03:00
Lauri Ojansivu 103e4dface Stage Git and archive sync before parallel destination mirroring
Synchronize Git to all active destinations before collecting source content. Reuse the local archive for concurrent destination processes, show combined per-mirror issue and release counters, and retain separate mirror-named logs. Stop content writes after archive failure and preserve restart checkpoints.
2026-09-14 13:58:58 +03:00
Lauri Ojansivu 587f4ec335 Record Galician received-card and scheduler translation repairs 2026-09-14 13:55:47 +03:00
Lauri Ojansivu cfc2480e54 Fix Galician received-card agreement and validate scheduled job labels 2026-09-14 13:55:27 +03:00
Lauri Ojansivu 927faa7215 Record Galician upload and accessibility review progress 2026-09-14 13:54:27 +03:00
Lauri Ojansivu f51c7439f9 Validate Galician upload and accessibility settings translations 2026-09-14 13:54:09 +03:00
Lauri Ojansivu b5ddb610a8 Update Galician diagnostic review progress 2026-09-14 13:53:41 +03:00
Lauri Ojansivu 8fbc620750 Validate Galician diagnostic and storage labels 2026-09-14 13:53:21 +03:00
Lauri Ojansivu 503ada2fbe Document LDAP optional-filter login repair and verification 2026-09-14 13:52:52 +03:00
Lauri Ojansivu 2c5cdd78a9 Handle unset optional LDAP user search filter
An unset LDAP_USER_SEARCH_FILTER was indexed after service-account binding, causing the reported undefined index TypeError instead of searching users. Preserve configured filters and reject invalid types explicitly.

Thanks to Nissulya and xet7 !

Fixes #6692,
2026-09-14 13:52:37 +03:00
Lauri Ojansivu 5b2ce8f6f3 Record Galician diagnostic and dictionary review progress 2026-09-14 13:51:54 +03:00
Lauri Ojansivu c6c3b90bef Clarify Galician V8 garbage overwrite diagnostic 2026-09-14 13:51:30 +03:00
Lauri Ojansivu 798e86ec68 Record Galician format translation review progress
Update dated evidence and exact counts while preserving unresolved dictionary and browser requirements.

Thanks to xet7 !
2026-09-14 13:48:33 +03:00
Lauri Ojansivu 1adfb3bec3 Validate Galician format instructions and request states
Retain 27 correct values preserving template tokens, space entities, spinner options and closed/resolved distinctions.

Thanks to xet7 !
2026-09-14 13:48:09 +03:00
Lauri Ojansivu 9d307d8518 Record Galician query translation review progress
Update dated evidence and exact counts while preserving remaining grammar and browser requirements.

Thanks to xet7 !
2026-09-14 13:47:29 +03:00
Lauri Ojansivu d2d90abf53 Validate Galician query syntax and troubleshooting instructions
Retain 30 correct values preserving shorthand, descending sort, period predicates and executable diagnostics.

Thanks to xet7 !
2026-09-14 13:47:04 +03:00
Lauri Ojansivu 414e846517 Record Galician entity translation review progress
Update dated evidence and exact counts, preserving remaining language and browser requirements.

Thanks to xet7 !
2026-09-14 13:46:26 +03:00
Lauri Ojansivu c78c7f3c52 Validate Galician entity and search predicate labels
Retain 30 correct values preserving entity names, date sorting and ended versus overdue predicates.

Thanks to xet7 !
2026-09-14 13:45:57 +03:00
Lauri Ojansivu e410234e78 Record Galician reminder translation review progress
Update dated evidence and exact counts, preserving remaining grammar and browser verification.

Thanks to xet7 !
2026-09-14 13:45:23 +03:00
Lauri Ojansivu 92b911aa94 Validate Galician reminders and notification controls
Retain 30 correct values preserving due conditions, exact tokens, read-state scope and weekday names.

Thanks to xet7 !
2026-09-14 13:44:57 +03:00
Lauri Ojansivu fe022ff608 Record Galician date activity translation review
Update dated evidence and exact counts while preserving outstanding grammar and browser validation.

Thanks to xet7 !
2026-09-14 13:44:27 +03:00
Lauri Ojansivu 44ff8c566b Validate Galician date activities and placement labels
Retain 30 correct values preserving authentication settings, date interpolation and opposing placement directions.

Thanks to xet7 !
2026-09-14 13:44:02 +03:00
Lauri Ojansivu cb16fd4d30 Record Galician rule translation review progress
Update dated evidence and exact counts while preserving remaining agreement and browser requirements.

Thanks to xet7 !
2026-09-14 13:43:32 +03:00
Lauri Ojansivu 87b9e45fb1 Validate Galician rule actions and web metadata labels
Retain 30 correct values preserving check/uncheck scope, wildcard meaning, date actions and literal HTML/JSON configuration names.

Thanks to xet7 !
2026-09-14 13:43:09 +03:00
Lauri Ojansivu 9530e99ea9 Record Galician custom-field activity repair progress
Update dated repair evidence and exact counts, retaining remaining grammar and browser verification.

Thanks to xet7 !
2026-09-14 13:42:35 +03:00
Lauri Ojansivu 29f1980b24 Clarify Galician custom-field value clearing activity
Describe removing the card field value rather than deleting its definition. Retain 25 correct rule and activity values, preserve percent placeholders and add positive/negative regression checks.

Thanks to xet7 !
2026-09-14 13:42:06 +03:00
Lauri Ojansivu 7f47bbf7f3 Record Galician permission translation review progress
Update dated evidence and exact counts while preserving unresolved agreement and browser requirements.

Thanks to xet7 !
2026-09-14 13:40:56 +03:00
Lauri Ojansivu dda51a1e5c Validate Galician account permissions and subtask settings
Retain 28 correct values preserving metadata, roles, notification scope and subtask path relationships. Leave received-label agreement open.

Thanks to xet7 !
2026-09-14 13:40:23 +03:00
Lauri Ojansivu 0b154538d3 Document compact release-note sections in Upcoming
Record the requested section whitelist and verification.

Thanks to xet7 !
2026-09-14 13:39:52 +03:00
Lauri Ojansivu f7def463be Restrict release notes to summary security and language updates
Emit only In short, Security, translation language names, thanks and the exact changelog anchor. Keep other details in the changelog and verify security retention, omitted developer entries and version-specific links offline.

Thanks to xet7 !
2026-09-14 13:39:32 +03:00
Lauri Ojansivu 6e9c26d53b Record Galician diagnostic translation review progress
Update dated evidence and exact counts while preserving remaining language and browser requirements.

Thanks to xet7 !
2026-09-14 13:37:55 +03:00
Lauri Ojansivu 6c534f40f6 Validate Galician SMTP and system diagnostic labels
Retain 30 correct values preserving WIP remedies, webhook scope, invitation tokens and diagnostic distinctions.

Thanks to xet7 !
2026-09-14 13:37:22 +03:00
Lauri Ojansivu 55a8bb26ca Record Galician shortcut translation review progress
Update dated evidence and exact counts, preserving outstanding language and browser requirements.

Thanks to xet7 !
2026-09-14 13:36:56 +03:00
Lauri Ojansivu 64a60f1897 Validate Galician shortcuts and custom branding labels
Retain 30 correct values preserving units, limits, shortcut actions and URL configuration distinctions.

Thanks to xet7 !
2026-09-14 13:36:34 +03:00
Lauri Ojansivu 1709b89e7b Record Galician selection translation review progress
Update dated evidence and exact counts while retaining outstanding grammar and browser validation.

Thanks to xet7 !
2026-09-14 13:36:06 +03:00
Lauri Ojansivu 885d65b3b8 Validate Galician selection and board-management labels
Retain 30 correct values preserving selection scope, role restrictions, login-link interpolation and image-only input.

Thanks to xet7 !
2026-09-14 13:35:43 +03:00
Lauri Ojansivu d07cc31d21 Record Galician filter translation review progress
Update dated evidence and exact counts, retaining outstanding grammar and browser requirements.

Thanks to xet7 !
2026-09-14 13:35:14 +03:00
Lauri Ojansivu c45e99b02b Validate Galician filters and import mapping labels
Retain 30 correct values preserving date ranges, list visibility, import formats and existing-user mapping.

Thanks to xet7 !
2026-09-14 13:34:48 +03:00
Lauri Ojansivu 88fa9f5461 Record Galician email translation review progress
Update dated evidence and exact counts, retaining unresolved contextual and browser requirements.

Thanks to xet7 !
2026-09-14 13:33:59 +03:00
Lauri Ojansivu ac0f8696fa Validate Galician account emails and export fields
Retain 29 correct values preserving email purposes, placeholders, export roles and attachment metadata.

Thanks to xet7 !
2026-09-14 13:33:18 +03:00
Lauri Ojansivu d3afacd15f Record Galician custom-field translation review progress
Update dated evidence and exact counts, preserving outstanding grammar and browser validation.

Thanks to xet7 !
2026-09-14 13:32:45 +03:00
Lauri Ojansivu 98c90fd8a6 Validate Galician custom fields and access labels
Retain 30 correct values preserving access restrictions, field types, dropdown alternatives and email interpolation.

Thanks to xet7 !
2026-09-14 13:32:20 +03:00
Lauri Ojansivu 3fb32bc4b5 Record Galician estimation translation review progress
Update dated evidence and exact counts while preserving remaining language and browser validation.

Thanks to xet7 !
2026-09-14 13:31:39 +03:00
Lauri Ojansivu 47fb0f2771 Validate Galician estimation controls and color labels
Retain 30 correct values preserving named features, automatic-width inversion and color distinctions.

Thanks to xet7 !
2026-09-14 13:31:11 +03:00
Lauri Ojansivu 51d025e131 Record Galician calendar translation review progress
Update dated evidence and counts, retaining outstanding language and browser requirements.

Thanks to xet7 !
2026-09-14 13:30:42 +03:00
Lauri Ojansivu e5e09f52c8 Validate Galician calendar and voting labels
Retain 30 correct values preserving date directions, voting scope and permanent deletion warnings.

Thanks to xet7 !
2026-09-14 13:30:19 +03:00
Lauri Ojansivu 31786d7574 Record Galician display translation review progress
Update dated evidence and exact counts while preserving outstanding language and browser validation.

Thanks to xet7 !
2026-09-14 13:29:52 +03:00
Lauri Ojansivu 708478423a Validate Galician display settings and archive controls
Retain 30 correct values preserving units, archive scope, avatar tokens and zoom directions.

Thanks to xet7 !
2026-09-14 13:29:29 +03:00
Lauri Ojansivu 00c917ab39 Record Galician activity translation review progress
Update dated evidence and exact counts, retaining outstanding fluency and browser requirements.

Thanks to xet7 !
2026-09-14 13:29:00 +03:00
Lauri Ojansivu 4b926a60fa Validate Galician activities and workspace controls
Retain 35 correct values preserving activity directions, date tokens, workspace settings and shortcut state inversions.

Thanks to xet7 !
2026-09-14 13:28:34 +03:00
Lauri Ojansivu 829fb2ae9d Record Galician migration execution translation review
Update dated evidence and exact counts; preserve outstanding language and browser verification.

Thanks to xet7 !
2026-09-14 13:28:03 +03:00
Lauri Ojansivu e8766c17b9 Validate Galician migration execution and account messages
Retain 30 correct values preserving background behavior, batch limits, attachment states and account validation.

Thanks to xet7 !
2026-09-14 13:27:41 +03:00
Lauri Ojansivu 81ed6d9899 Document language-only translation release notes
Record summary behavior and offline verification under Upcoming.

Thanks to xet7 !
2026-09-14 13:27:11 +03:00
Lauri Ojansivu 8db16132cb Summarize release-note translations as updated languages
Keep detailed translation entries in the changelog and audit, but list only affected languages in release notes. Require explicit language metadata, preserve other topic details, and anchor release heading extraction. Verify nested detail removal and missing-metadata failure offline.

Thanks to xet7 !
2026-09-14 13:26:55 +03:00
Lauri Ojansivu a187b70013 Record Galician migration translation review progress
Update dated evidence and exact counts while preserving open completion terminology and browser requirements.

Thanks to xet7 !
2026-09-14 13:25:13 +03:00
Lauri Ojansivu ff77543b5c Validate Galician migration scopes and confirmations
Retain 34 correct values preserving archive restrictions, duplicate-list conditions, administrator-only execution and migration progress.

Thanks to xet7 !
2026-09-14 13:24:53 +03:00
Lauri Ojansivu 72abc938dd Record Galician account and scheduling review progress
Update dated exact counts and remaining ambiguous terminology requirements.

Thanks to xet7 !
2026-09-14 13:24:23 +03:00
Lauri Ojansivu 3b524f0712 Validate Galician account states and scheduled board actions
Retain 29 correct values preserving activation, unlock scope and scheduling success versus execution. Leave Complete wording open pending context.

Thanks to xet7 !
2026-09-14 13:23:57 +03:00
Lauri Ojansivu fd170dfc32 Record Galician storage translation review progress
Update dated evidence and resolve visibility-label context while preserving remaining validation scope.

Thanks to xet7 !
2026-09-14 13:23:33 +03:00
Lauri Ojansivu 2f141de75b Validate Galician storage and account-lockout labels
Retain 30 correct values preserving attachment scope, storage names, account states and visibility settings. Resolve card-show-lists context against the settings row and toggle.

Thanks to xet7 !
2026-09-14 13:23:08 +03:00
Lauri Ojansivu 4b82dd70fb Document changelog-only release notes in Upcoming
Record the release-note fix, validation and preserved separate provenance artifacts.

Thanks to xet7 !
2026-09-14 13:22:38 +03:00
Lauri Ojansivu ccfceb7b77 Exclude binary provenance tables from release notes
Generate initial and refreshed release notes solely from the changelog. Retain checksum verification and build provenance artifacts separately. Update build entry points, release instructions and regression guards.

Thanks to xet7 !
2026-09-14 13:22:19 +03:00
Lauri Ojansivu 263da8892d Record Galician predicate translation review progress
Update dated evidence and remaining contextual review requirements.

Thanks to xet7 !
2026-09-14 13:20:49 +03:00
Lauri Ojansivu f22aeb7bcd Validate Galician search predicates and checklist instructions
Retain 39 correct values preserving boolean search logic, deletion constraints, checklist order and legal-notice agreement. Leave card-show-lists context open.

Thanks to xet7 !
2026-09-14 13:20:19 +03:00
Lauri Ojansivu 7672a79147 Record Galician search translation review progress
Update dated review evidence and exact counts while retaining remaining language and browser requirements.

Thanks to xet7 !
2026-09-14 13:19:50 +03:00
Lauri Ojansivu cf1e89bb9e Validate Galician search results and operator instructions
Retain 40 correct values preserving query syntax, role alternatives, date thresholds and result interpolation.

Thanks to xet7 !
2026-09-14 13:19:28 +03:00
Lauri Ojansivu 93ea560109 Record Galician deletion and card-view review progress
Update dated evidence and exact review counts, preserving outstanding grammar and browser validation.

Thanks to xet7 !
2026-09-14 13:18:59 +03:00
Lauri Ojansivu 2cb3f6b13f Validate Galician deletion warnings and personal card views
Retain 34 correct values, preserving irreversible effects, linked-card deletion order, mention placeholders and due-card permission restrictions.

Thanks to xet7 !
2026-09-14 13:18:38 +03:00
Lauri Ojansivu f8bb9f02f7 Record Galician date-trigger repairs and grammar finding
Update dated progress, source commit and remaining contextual agreement requirements.

Thanks to xet7 !
2026-09-14 13:18:14 +03:00
Lauri Ojansivu 54fdfd33ca Repair Galician automation date-trigger meanings
Replace Portuguese date wording and preserve both setting and changing dates. Retain 39 correct automation and HTML-placement values; leave attachment participle agreement open after inspecting assembled controls. Add positive and negative date-trigger regression checks.

Thanks to xet7 !
2026-09-14 13:17:47 +03:00
Lauri Ojansivu 6100709231 Record Galician card settings translation review progress
Update dated review evidence and exact counts while retaining outstanding language and browser validation.

Thanks to xet7 !
2026-09-14 13:16:37 +03:00
Lauri Ojansivu edea4b20e1 Validate Galician card settings and automation labels
Retain 25 correct translations after checking parent relationships, subtask destinations, attachment counts, activity interpolation and automation action scope.

Thanks to xet7 !
2026-09-14 13:16:13 +03:00
Lauri Ojansivu 0a43570fc8 Record Galician visibility translation review progress
Update dated review hash and exact counts, preserving remaining language and browser requirements.

Thanks to xet7 !
2026-09-14 13:14:09 +03:00
Lauri Ojansivu e74cc64642 Validate Galician visibility and deletion warnings
Retain twenty-five correct values, preserving private-only scope, field application distinctions, invitation tokens and irreversible deletion warnings.

Thanks to xet7 !
2026-09-14 13:13:40 +03:00
Lauri Ojansivu 115901a7c8 Record Galician removal and shortcut translation review
Update dated review hash and exact counts while preserving all remaining audit and browser requirements.

Thanks to xet7 !
2026-09-14 13:13:08 +03:00
Lauri Ojansivu 4f0293f5cd Validate Galician removal and card-shortcut labels
Retain twenty-five correct translations, preserving member-removal effects, unsaved-description warnings, thresholds and self-assignment distinctions.

Thanks to xet7 !
2026-09-14 13:12:44 +03:00
Lauri Ojansivu 91c36f1cea Record Galician import-mapping and privacy translation review
Update dated review hash and exact counts while preserving remaining full audit and browser scope.

Thanks to xet7 !
2026-09-14 13:12:13 +03:00
Lauri Ojansivu 7f7db419c7 Validate Galician import mapping and board privacy descriptions
Retain twenty-five correct translations, preserving admin constraints, mapping fallback, notification scope and public versus private access.

Thanks to xet7 !
2026-09-14 13:11:37 +03:00
Lauri Ojansivu b6403e798e Record Galician import-help translation repair
Update dated repair hash and exact counts while preserving remaining full language and browser audit scope.

Thanks to xet7 !
2026-09-14 13:11:10 +03:00
Lauri Ojansivu 316232f694 Preserve Trello navigation labels in Galician import help
Retain Galician prose and twenty-nine correct template, invitation, export and filter labels. Preserve literal JSON properties and interpolation.

Thanks to xet7 !
2026-09-14 13:10:42 +03:00
Lauri Ojansivu 0233b49593 Record Galician role-description translation review
Update dated review hash and exact counts, preserving remaining full language and browser audit scope.

Thanks to xet7 !
2026-09-14 13:10:03 +03:00
Lauri Ojansivu 96367d2134 Validate Galician role descriptions and card controls
Retain twenty-five correct translations, preserving assigned-only visibility, editing restrictions and worker self-assignment scope.

Thanks to xet7 !
2026-09-14 13:09:36 +03:00
Lauri Ojansivu c8adbff586 Record Galician deletion-warning translation review
Update dated review hash and exact counts while preserving remaining audit and browser requirements.

Thanks to xet7 !
2026-09-14 13:08:58 +03:00
Lauri Ojansivu 4c585a2b81 Validate Galician deletion warnings and archive guidance
Retain twenty-five correct values, preserving permanent deletion versus reversible archive behavior and activity-loss warnings.

Thanks to xet7 !
2026-09-14 13:08:36 +03:00
Lauri Ojansivu 646ab17f46 Record Galician board-settings translation review
Update dated review hash and exact counts, preserving full remaining audit and browser scope.

Thanks to xet7 !
2026-09-14 13:08:04 +03:00
Lauri Ojansivu 68277a708d Validate Galician board settings and archive controls
Retain twenty-five correct translations, preserving member and assignee scopes, public/private distinctions and HTML emphasis.

Thanks to xet7 !
2026-09-14 13:07:41 +03:00
Lauri Ojansivu e55898711f Record Galician creation-control translation review
Update dated review hash and exact counts while preserving all unresolved language and browser requirements.

Thanks to xet7 !
2026-09-14 13:07:13 +03:00
Lauri Ojansivu f1add7ab2b Validate Galician creation controls and offline warning
Retain twenty-five correct translations, preserving placement, privilege scope, count agreement and data-loss conditions.

Thanks to xet7 !
2026-09-14 13:06:49 +03:00
Lauri Ojansivu 36b0109fc3 Record Galician membership and movement translation review
Update dated review hash and exact counts while preserving unresolved language and browser verification.

Thanks to xet7 !
2026-09-14 13:06:22 +03:00
Lauri Ojansivu b82f539a41 Validate Galician membership and card-movement activities
Retain twenty-five correct archive, import, movement and checklist values, preserving direction and exact interpolation.

Thanks to xet7 !
2026-09-14 13:05:59 +03:00
Lauri Ojansivu 2998b14bc2 Record Galician activity translation review progress
Update dated review hash and exact counts while preserving uncertain Esperanto findings and full remaining language/browser scope.

Thanks to xet7 !
2026-09-14 13:05:31 +03:00
Lauri Ojansivu 0d2e977d90 Validate Galician card and checklist activity translations
Retain twenty-five correct activity values, preserving action distinctions, object relationships and exact placeholders.

Thanks to xet7 !
2026-09-14 13:05:08 +03:00
Lauri Ojansivu 0b8f090343 Record Esperanto overtime terminology repair and evidence limits
Update dated repair hash and counts, distinguishing work classification from deadline expiry and retaining broader fluency review.

Thanks to xet7 !
2026-09-14 13:04:34 +03:00
Lauri Ojansivu 9b6b1ffe6f Clarify Esperanto overtime work labels
Use consistent overtime-work terminology for manually classified recorded hours, preserving the hours unit and separating this flag from due-date expiry.

Thanks to xet7 !
2026-09-14 13:04:06 +03:00
Lauri Ojansivu 11ac7f8a97 Record Esperanto advanced-filter translation repair
Update dated repair hash, exact counts and parser evidence while preserving wider language and browser review requirements.

Thanks to xet7 !
2026-09-14 13:03:28 +03:00
Lauri Ojansivu 5d94f1b92b Repair Esperanto advanced-filter examples and escaping instructions
Preserve canonical query examples and explain literal special-character escaping. Add positive example and negative malformed-wording coverage.

Thanks to xet7 !
2026-09-14 13:03:02 +03:00
Lauri Ojansivu e520065604 Record confirmed Esperanto scrollbar terminology
Update dated review hash and primary-source evidence, retaining unresolved color and broader audit requirements.

Thanks to xet7 !
2026-09-14 13:02:10 +03:00
Lauri Ojansivu 41fb26dff6 Validate Esperanto scrollbar terminology against computing references
Retain the correct vertical scrollbar translation; primary terminology and LibreOffice documentation confirm rulumskalo.

Thanks to xet7 !
2026-09-14 13:01:46 +03:00
Lauri Ojansivu c91ef98426 Record Esperanto garbage-zapping terminology repair
Update dated repair hash, official API evidence and exact counts while keeping remaining translation review open.

Thanks to xet7 !
2026-09-14 13:01:18 +03:00
Lauri Ojansivu 05217bbb9e Correct Esperanto heap garbage-zapping diagnostic
Describe overwriting heap garbage with a bit pattern rather than zeroing it, matching official Node V8 documentation.

Thanks to xet7 !
2026-09-14 13:00:52 +03:00
Lauri Ojansivu d8fd82d57d Record Esperanto remaining-label review progress
Update dated review hash and exact counts; explicitly preserve nine uncertain Esperanto findings and wider audit scope.

Thanks to xet7 !
2026-09-14 13:00:18 +03:00
Lauri Ojansivu f523001c3e Validate Esperanto account outcomes and remaining basic labels
Retain ten correct labels, preserving account creation success versus failure and login versus logout distinctions.

Thanks to xet7 !
2026-09-14 12:59:51 +03:00
Lauri Ojansivu ccafc4db96 Record Esperanto repository translation review progress
Update dated review hash and counts, preserving all remaining language and browser audit requirements.

Thanks to xet7 !
2026-09-14 12:59:23 +03:00
Lauri Ojansivu 9ba7cfcb80 Validate Esperanto repository and authentication labels
Retain thirty correct values, preserving temporary lockout conditions, required credentials, minimum length and byte units.

Thanks to xet7 !
2026-09-14 12:58:58 +03:00
Lauri Ojansivu 545e1bc9fb Record Esperanto scheduling translation review progress
Update dated review hash and exact counts; group upcoming translation entries and preserve open verification requirements.

Thanks to xet7 !
2026-09-14 12:58:32 +03:00
Lauri Ojansivu 5a398d5acc Validate Esperanto scheduling intervals and diagnostics
Retain thirty correct restoration, resource and scheduling labels, preserving interval quantities, singular/plural agreement and percent units.

Thanks to xet7 !
2026-09-14 12:57:52 +03:00
Lauri Ojansivu 44a6f7e74a Record Esperanto migration-confirmation review progress
Update dated review hash and counts, preserving unresolved meanings and full remaining audit scope.

Thanks to xet7 !
2026-09-14 12:57:26 +03:00
Lauri Ojansivu 07756eecd0 Validate Esperanto migration confirmations and steps
Retain thirty correct warnings and progress labels, preserving operation order, archived versus nonarchived scope and difficult-undo warnings.

Thanks to xet7 !
2026-09-14 12:57:00 +03:00
Lauri Ojansivu ba489652ec Record Esperanto migration-description review progress
Update dated review hash, exact counts and unresolved completion-label evidence without closing remaining audit scope.

Thanks to xet7 !
2026-09-14 12:56:33 +03:00
Lauri Ojansivu 036cbff311 Validate Esperanto migration repair descriptions
Retain nineteen correct restoration and data-integrity labels, preserving field identifiers, board scope and administrator-only permission. Leave Complete state terminology under review.

Thanks to xet7 !
2026-09-14 12:56:08 +03:00
Lauri Ojansivu aa0ae67f83 Record Esperanto storage and migration review progress
Update dated review hash and exact counts while preserving remaining translation and browser requirements.

Thanks to xet7 !
2026-09-14 12:55:32 +03:00
Lauri Ojansivu 460df4ada0 Validate Esperanto storage and migration settings
Retain thirty correct values, preserving conjunctive duplicate-list safety conditions and distinct migration actions and connection outcomes.

Thanks to xet7 !
2026-09-14 12:55:10 +03:00
Lauri Ojansivu 25ed0a5ccb Record Esperanto scheduled-job translation review progress
Update dated review hash and counts, preserving released v11.77 notes and recording new work under Upcoming.

Thanks to xet7 !
2026-09-14 12:54:44 +03:00
59 changed files with 12954 additions and 238 deletions

No files matched your search

+7 -18
View File
@@ -1063,7 +1063,7 @@ jobs:
path: provenance
merge-multiple: true
- name: Compose the release notes, provenance first
- name: Compose release notes from the changelog only
# The notes go from FILE to FILE and never become a shell value. Two
# earlier shapes each broke a release: inline `${{ … }}` put them in the
# shell SOURCE, so a backtick in any `code` span ran as a command (v10.59
@@ -1073,8 +1073,7 @@ jobs:
# from CHANGELOG.md, appended with cat, the text is data all the way.
run: |
set -euo pipefail
bash releases/provenance-table.sh > release-notes.md || true
bash releases/release-notes.sh "$VERSION" >> release-notes.md
bash releases/release-notes.sh "$VERSION" > release-notes.md
echo "----- release notes -----"
head -40 release-notes.md
@@ -5317,15 +5316,8 @@ jobs:
echo "::error::nextcloud FAILED. The ::error:: line in this job's log, above, says which step and why."
fi
# ── The provenance table, completed ─────────────────────────────────────────
#
# The release is CREATED before win64, mac-arm64 and the extra architectures
# are built - those jobs need a release to upload their bundles to - so the
# table it is created with can only cover amd64 and arm64. This runs after all
# of them and rewrites the notes with every bundle's rows in.
#
# always(): a platform that failed should not cost the release its table for
# the platforms that succeeded.
# Refresh the selected changelog notes after builds. Binary provenance remains
# in build artifacts and is never appended to release notes.
release-notes:
name: complete the release notes
needs:
@@ -5359,7 +5351,7 @@ jobs:
path: provenance
merge-multiple: true
- name: Rewrite the notes with every bundle in the table
- name: Refresh release notes from the changelog only
# File to file, never a shell value: see the "Compose the release notes"
# step for the two ways that broke a release.
env:
@@ -5367,10 +5359,7 @@ jobs:
VERSION: ${{ needs.prepare.outputs.version }}
run: |
set -euo pipefail
rows=$(find provenance -name '*.tsv' 2>/dev/null | wc -l)
echo "Provenance files collected: $rows"
bash releases/provenance-table.sh > release-notes.md || true
bash releases/release-notes.sh "$VERSION" >> release-notes.md
bash releases/release-notes.sh "$VERSION" > release-notes.md
gh release edit --repo "${GITHUB_REPOSITORY:-wekan/wekan}" "v${VERSION}" --notes-file release-notes.md
echo "Release notes updated with the provenance of every bundle built."
echo "Release notes updated from the changelog; binary provenance stays in build artifacts."
head -40 release-notes.md
+11 -13
View File
@@ -555,19 +555,17 @@ directly after the merge.
`<details>` blocks below carry that information. Keep the paragraph current as
topics change, and shorten it when added commits make it grow. A finished release
keeps the paragraph it was written with.
- **No release section carries a Platform/Binary/From/Version/SHA256 table, and
none ever should again.** It used to sit right under the `**In short:**`
paragraph, then moved to a `**Binaries in these bundles:**` label at the end
of the section — both tried and both removed, because CHANGELOG.md is not
where that data belongs: it made every release's entry mostly a giant table
nobody read, on top of what `<details>` entries already say. "Which Node.js
is in the arm64 bundle of 10.69, and was it checked" is answered by the
**GitHub Release notes** instead: `releases/provenance-table.sh` puts the
same table at the TOP of those, built fresh from the `provenance.tsv` rows
each build job records (see `releases/record-provenance.sh`), every time a
release is made — so it is never stale and never needs hand-editing into
CHANGELOG.md. Adding a binaries table back into a CHANGELOG entry, in any
shape, is reintroducing something the maintainer removed on purpose.
- **Neither CHANGELOG.md nor release notes may contain a binaries provenance
section or a Platform/Bundle/Binary/From/Version/Checked/SHA256 table.**
Do not add "Binaries in these bundles" at the top or bottom of either.
Release notes summarize each Translations group as only a list of updated
languages. Add `**Languages updated:**` with comma-separated full language
names below the group label; keep detailed entries in the changelog and audit.
Release notes have only In short, Security, Translations (language list),
the standard thanks line and More details at ChangeLog with the release
anchor link. Other details stay in CHANGELOG.md. Keep download
verification and `provenance.tsv` build artifacts for diagnostics; do not
append their contents or `releases/provenance-table.sh` output to notes.
- **Inside a subsection, entries are GROUPED BY TOPIC/AREA.** A release touches a handful
of areas and repeating the area's name in every summary is the noise this
removes — twelve entries that each begin "All Boards:" say "All Boards" twelve
+860 -7
View File
@@ -80,9 +80,12 @@ held only issues \#4774 and \#4055, and both are closed now.
Resumed at the maintainer's request on 2026-09-13, beginning with Klingon.
The audit tracks 20,081
findings: 15,555 corrected, 2,237 restored pre-pull values awaiting
validation, 1,981 reviewed and retained, and 308 pending review or repair.
The correction inventory records 18,151 exact before/after values,
findings: 15,571 corrected, 466 restored pre-pull values awaiting
validation, 3,736 reviewed and retained, and 308 pending review or repair.
The correction inventory records 18,173 exact before/after values,
Global completeness verification also identifies Veps `server` (currently
English-identical “Server”) for terminology review; it is not accepted yet.
including repairs outside the original findings.
All 361 originally flagged Klingon findings are repaired. The broader review
of 829
@@ -106,10 +109,11 @@ performed. Preserve correct translations and source placeholders during repairs.
The requested organization, linked-file/static-archive and rate-limit mirror
changes are implemented in local commit 02383521a; translation repairs have
resumed.
Latest translation fix/review is 6eaabcaea (2026-09-14): Esperanto failure
time window clarified; 29 correct support and account labels retained.
Latest unchanged-value review is 1439290a6 (2026-09-14): 30 Esperanto
attachment, upload, authentication and custom-translation labels retained.
Latest translation fix/review is 29f1980b2 (2026-09-14): Galician field
value clearing clarified; 25 correct rule/activity values retained.
Latest unchanged-value review is 1adfb3bec (2026-09-14): 27 Galician
format and request-state values retained. Shared attachment/member
participles still need contextual agreement repair.
Of 611 restored Danish values reviewed, 606 are retained and five repaired.
Latest unchanged-value reviews are 944243917 and ed9be5431 (2026-09-14),
retaining 156 correct Danish storage, migration and monitoring/account labels.
@@ -630,6 +634,855 @@ the Markdown commit as the template.
</details>
</details>
# v11.78 2026-09-14 WeKan ® release
**In short:** **Board views** use the full available width, and Frappe Gantt
avoids a browser-blocking translation observer loop. **LDAP login** handles an
unset search filter after service-account binding. **Activity feeds** substitute
message values while preserving security-aware rendering. **Language selection**
uses full-width, two-line entries. **Developer tools** recover interrupted Git
pulls, synchronize mirrors in stages and produce compact release notes.
**Translations** repair Basque, Esperanto and Galician wording; broader language
and browser validation remains under review.
This release fixes the following bugs:
**LDAP login** - Optional user search filter.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/2c5cdd78a">Handle an unset filter after service-account binding.</a>. Thanks to Nissulya and xet7.</summary>
An unset optional LDAP_USER_SEARCH_FILTER caused an undefined-index TypeError
before searching users. Configured restrictions remain active. Regression,
encryption, connection-release and redacted error-logging checks pass.
Live directory login remains unverified.
Fixes #6692,
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/dad65fb6c">Verify the LDAP post-bind fix through actual user search methods.</a>. Thanks to Nissulya and xet7.</summary>
The latest 11.77 report predates the local fix. Exercise binding followed
by user search with unset and configured filters, and verify failed binding
stops search. The separate oplog observer error remains unproven as a login
cause. Offline regression passes; live directory confirmation remains open.
Fixes #6692,
</details>
**Board layout and navigation** - Full-width content and responsive Gantt.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/fda197bb0">Full-width board report views</a>. Thanks to xet7.</summary>
Timeline, Time, Statistics, grouping and chart views use the full available
board width instead of centered 900px or 1100px content caps. Content padding
stays inside the available width. Regression tests cover the shared shells;
a browser test checks Timeline, Time and Statistics at desktop and mobile widths.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/670c20311">Keep Frappe Gantt responsive</a>. Thanks to xet7.</summary>
The header translation observer skips identical text updates, preventing an
endless mutation loop when English labels already match their translations.
Tests cover English and translated labels; a browser regression opens Frappe
Gantt and switches back to Swimlanes.
</details>
**Language picker** - Full-width two-line entries.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c3ce54fa2">Show language and regional flags on separate lines.</a>. Thanks to xet7.</summary>
The popup fills the viewport width, with a language flag/name first and
parenthesized country flag/name below. Wider responsive columns and wrapping
prevent clipped names. All 245 locale/flag checks pass. Browser assertions
cover full width, narrow windows and RTL order; syntax checked but not run live.
</details>
**Activity feeds** - Substitute message values.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/888e0e6bc">Show activity titles and labels instead of literal percent placeholders.</a>. Thanks to xet7.</summary>
Card and right-sidebar activities now pass values through the current
translation API's sprintf options. Discard Spacebars helper metadata while
preserving security-aware formatting and sanitizing. Real formatter tests
cover labels, moves, comments, attachment deletion and link stripping.
Both-feed browser assertions are registered and syntax-checked; live browser
execution was not available.
</details>
and improves the following developer tooling:
**Release workflow** - Changelog-only release notes.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/ccfceb7b7">Exclude binary provenance tables from release notes</a>. Thanks to xet7.</summary>
Initial and refreshed release notes contain only the selected changelog
section. Keep checksum verification and provenance build artifacts separately.
Update build.sh, build.bat, release-all.sh and repository instructions.
Release-note, binary-source, menu-parity and shell syntax checks pass.
No release was published; native Windows execution was not tested.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/8db16132c">Summarize release-note translations as updated languages</a>. Thanks to xet7.</summary>
Release notes list only affected languages for translation updates. Keep
full translation entries in the changelog and audit; preserve other release
sections. Explicit language metadata prevents guessed names. Offline tests
verify nested detail removal, non-translation preservation, missing metadata
and exact release-heading selection. No release was published.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f7def463b">Restrict release notes to summary, security and language updates</a>. Thanks to xet7.</summary>
Release notes contain only In short, Security, updated translation languages,
the standard thanks line and More details at ChangeLog with the release
anchor. Keep full entries in the changelog. Offline tests verify retained
security details, omitted other entries, exact headings and version links.
Script parity and shell syntax checks pass; no release was published.
</details>
**Git menu** - Recover interrupted pulls and preserve commit links.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f665f7cda">Recover interrupted Git menu operations safely</a>. Thanks to xet7.</summary>
Pull and push stop before an existing rebase, merge or Git lock. Failed rebase
cleanup reports the remaining operation instead of claiming it was aborted.
Fast-forward pulls can autostash tracked edits; pushes check fetch errors and
integrate newer origin commits before publishing. Offline regression tests
cover operation guards and cleanup behavior.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f23a18515">Automate interrupted pull recovery and merge integration</a>. Thanks to xet7.</summary>
The Git menu preserves an orphaned autostash and removes only the verified
incomplete rebase setup. Diverged branches merge origin with automatic merge
commits, preserving existing changelog commit hashes. Existing changelog link
repair still commits repairs before a human-run push. Actual content conflicts
remain visible for resolution, and active operations or locks stop recovery.
</details>
**Mirroring** - Staged synchronization and parallel target progress.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/103e4dfac">Synchronize Git, archive locally, then mirror content concurrently.</a>. Thanks to xet7.</summary>
Option 1 synchronizes Git with every active target before source content
collection. After the local archive succeeds, target content processes run
concurrently with combined start-time and issue/release counters. Separate
mirror-named text logs retain each target's output. Restart checkpoints skip
completed content targets. Offline concurrency, failure, restart, archive,
attachment and rate-limit tests pass. No remote synchronization was run.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/44e3e1fd7">Recover unavailable linked files from historical captures.</a>. Thanks to xet7.</summary>
Use archive.org history near the issue/comment creation date when public
links are unavailable. Bound attachment requests to 30 seconds, defer long
cooldowns without bypassing rate limits, and skip optional network failures.
Keep recovered-file provenance and existing bytes. Offline fallback, private
link, attachment, archive, restart and rate-limit tests pass. No remote
synchronization was run.
</details>
and includes the following translation repairs and reviews:
**Translations** - Terminology, instructions and activity validation.
**Languages updated:** Basque, Esperanto, Galician
<details>
<summary><a href="https://github.com/wekan/wekan/commit/316232f69">Preserve Trello navigation labels in Galician import help</a>. Thanks to xet7.</summary>
Keep literal navigation labels with Galician prose. Retain 29 correct
invitation, template, export and filter values. Correction and review
checks pass; broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/9b6b1ffe6">Clarify Esperanto overtime work labels</a>. Thanks to xet7.</summary>
Use consistent overtime-work wording for manually classified recorded
hours. Preserve the hours unit and distinguish the flag from deadline
expiry. Correction checks pass; wider fluency/browser review remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/5d94f1b92">Repair Esperanto advanced-filter examples and escaping</a>. Thanks to xet7.</summary>
Preserve canonical query examples and explain special-character escaping.
Positive examples and negative wording checks pass with correction checks;
broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/05217bbb9">Correct Esperanto heap garbage-zapping diagnostic</a>. Thanks to xet7.</summary>
Describe heap garbage overwritten with a bit pattern instead of zeroing,
matching official Node V8 documentation. All correction records pass;
wider language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/5946767b1">Validate Esperanto scheduled-job and migration labels</a>. Thanks to xet7.</summary>
Retain 29 correct administration values. Failed/paused and success/failure
states remain distinct. Review checks pass; isolated Complete and broader
language/browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/460df4ada">Validate Esperanto storage and migration settings</a>. Thanks to xet7.</summary>
Retain 30 correct labels and instructions. Preserve the empty-list AND
same-title populated-list condition, migration action distinctions and
connection success/failure. Review checks pass; broader validation is open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/036cbff31">Validate Esperanto migration repair descriptions</a>. Thanks to xet7.</summary>
Retain 19 correct restoration and repair translations. Preserve literal
field identifiers, board scope and administrator-only permission. Review
checks pass; ambiguous completion labels and broader validation stay open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/07756eecd">Validate Esperanto migration confirmations and steps</a>. Thanks to xet7.</summary>
Retain 30 correct warnings and progress labels. Preserve archived versus
nonarchived scope, conversion before deletion and difficult-undo warnings.
Review checks pass; broader language and browser verification stays open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/5a398d5ac">Validate Esperanto scheduling intervals and diagnostics</a>. Thanks to xet7.</summary>
Retain 30 correct restoration, scheduling and resource labels. Preserve
interval quantities, singular/plural agreement, run-once scope and percent
units. Review checks pass; broader validation remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/9ba7cfcb8">Validate Esperanto repository and authentication labels</a>. Thanks to xet7.</summary>
Retain 30 correct labels and warnings. Preserve temporary lockout from
failed logins, required credentials, minimum username length and byte units.
Review checks pass; wider language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f523001c3">Validate Esperanto account outcomes and basic labels</a>. Thanks to xet7.</summary>
Retain ten correct labels, preserving account creation success/failure and
login/logout distinctions. Review checks pass; nine uncertain Esperanto
findings and broader language/browser verification remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/41fb26dff">Validate Esperanto scrollbar terminology</a>. Thanks to xet7.</summary>
Retain the correct vertical-scrollbar translation, supported by Komputeko
and LibreOffice terminology. Review checks pass; broader review stays open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/0d2e977d9">Validate Galician card and checklist activities</a>. Thanks to xet7.</summary>
Retain 25 correct activity translations. Preserve action distinctions,
object relationships and exact placeholders. Review checks pass; broader
language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/b82f539a4">Validate Galician membership and card-movement activities</a>. Thanks to xet7.</summary>
Retain 25 correct activity translations. Preserve movement direction,
old/new object references and checklist states. Review checks pass;
broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f1add7ab2">Validate Galician creation controls and offline warning</a>. Thanks to xet7.</summary>
Retain 25 correct translations. Preserve placement, privilege scope,
singular/plural counts and data-loss conditions. Review checks pass;
broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/68277a708">Validate Galician board settings and archive controls</a>. Thanks to xet7.</summary>
Retain 25 correct translations. Preserve member/assignee scopes,
public/private distinctions and HTML emphasis. Review checks pass;
broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4c585a2b8">Validate Galician deletion warnings and archive guidance</a>. Thanks to xet7.</summary>
Retain 25 correct translations. Preserve permanent deletion, activity loss
and no-undo warnings versus reversible archive guidance. Review checks
pass; broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/96367d213">Validate Galician role descriptions and card controls</a>. Thanks to xet7.</summary>
Retain 25 correct translations. Preserve assigned-only visibility,
editing restrictions and worker self-assignment scope. Review checks pass;
broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/7f7db419c">Validate Galician import mapping and board privacy descriptions</a>. Thanks to xet7.</summary>
Retain 25 correct values. Preserve mapping fallback, last-admin constraint,
notification scope and public-view/member-only-edit distinctions. Review
checks pass; broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4f0293f5c">Validate Galician removal and card-shortcut labels</a>. Thanks to xet7.</summary>
Retain 25 correct values. Preserve removal effects, unsaved-description
warnings, thresholds and self-assignment distinctions. Review checks pass;
broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/e74cc6464">Validate Galician visibility and deletion warnings</a>. Thanks to xet7.</summary>
Retain 25 correct values. Preserve private-only scope, invitation tokens,
field application distinctions and irreversible deletion warnings. Review
checks pass; broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/edea4b20e">Validate Galician card settings and automation labels</a>. Thanks to xet7.</summary>
Retain 25 correct values. Preserve parent relationships, subtask destinations,
attachment counts, activity tokens and automation action scope. Review checks
pass; broader language and browser verification remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/54fdfd33c">Repair Galician automation date-trigger meanings</a>. Thanks to xet7.</summary>
Four date-trigger conditions now preserve both setting and changing dates
in Galician, replacing Portuguese wording. Retain 39 correct automation and
HTML-placement values. Positive/negative regression and ledger checks pass;
shared attachment/member participle agreement and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/2cb3f6b13">Validate Galician deletion warnings and personal card views</a>. Thanks to xet7.</summary>
Retain 34 correct values. Preserve irreversible deletion, linked-card removal
order, mention tokens and due-card permission restrictions. Review checks
pass; broader language and browser verification remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/cf1e89bb9">Validate Galician search results and operator instructions</a>. Thanks to xet7.</summary>
Retain 40 correct values. Preserve query syntax, quoted examples, result
tokens, role alternatives and created/modified day thresholds. Review checks
pass; broader language and browser verification remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f22aeb7bc">Validate Galician search predicates and checklist instructions</a>. Thanks to xet7.</summary>
Retain 39 correct values. Preserve boolean search logic, absence checks,
positive limits, deletion constraints, checklist order and legal agreement.
Review checks pass; field-context and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/2f141de75">Validate Galician storage and account-lockout labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve attachment/storage scope, account states
and support visibility. Resolve card-show-lists wording against its settings
row and toggle. Review checks pass; broader browser validation remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/3b524f071">Validate Galician account states and scheduled board actions</a>. Thanks to xet7.</summary>
Retain 29 correct values. Preserve activation, unlock scope and successful
scheduling versus completed execution. Review checks pass; Complete wording
and broader language/browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/ff77543b5">Validate Galician migration scopes and confirmations</a>. Thanks to xet7.</summary>
Retain 34 correct values. Preserve duplicate-list conditions, archive scope,
repair order, administrator restrictions and progress meanings. Review checks
pass; completion terminology and broader browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/e8766c17b">Validate Galician migration execution and account messages</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve background execution, batch limits,
attachment states, account validation and activity tokens. Review checks
pass; broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4b926a60f">Validate Galician activities and workspace controls</a>. Thanks to xet7.</summary>
Retain 35 correct values. Preserve action directions, date interpolation,
workspace settings, layout scope and keyboard shortcut inversions. Review
checks pass; broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/708478423">Validate Galician display settings and archive controls</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve pixel/integer constraints, archive scope,
avatar tokens, mobile/desktop switching and opposing zoom directions.
Review checks pass; broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/e5e09f52c">Validate Galician calendar and voting labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve month directions, date distinctions,
voting eligibility, visibility and permanent deletion warnings. Review checks
pass; broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/47fb0f277">Validate Galician estimation controls and color labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve Planning Poker actions, feature names,
automatic-width inversion, clipboard alternatives and color distinctions.
Review checks pass; broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/98c90fd8a">Validate Galician custom fields and access labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve access restrictions, custom-field types,
dropdown alternatives, WIP limits and account email interpolation. Review
checks pass; broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/ac0f8696f">Validate Galician account emails and export fields</a>. Thanks to xet7.</summary>
Retain 29 correct values. Preserve email purposes and tokens, account states,
export roles, attachment metadata and sorting scope. Review checks pass;
export free wording and broader browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c45e99b02">Validate Galician filters and import mapping labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve relative date filters, list visibility,
import formats and existing-user mapping. Review checks pass; broader
language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/885d65b3b">Validate Galician selection and board-management labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve selection scope, role restrictions,
private-page login interpolation and image-only input. Review checks pass;
broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/64a60f189">Validate Galician shortcuts and custom branding labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve shortcut actions, task/hour limits,
upload states and logo/URL configuration distinctions. Review checks pass;
broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/6c534f40f">Validate Galician SMTP and system diagnostic labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve WIP remedies, SMTP purpose, invitation
tokens, webhook scope and system-diagnostic distinctions. Review checks pass;
broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/dda51a1e5">Validate Galician account permissions and subtask settings</a>. Thanks to xet7.</summary>
Retain 28 correct values. Preserve permissions, metadata, role attribution,
notification scope and subtask path relationships. Review checks pass;
received-label agreement and broader browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/29f1980b2">Clarify Galician custom-field value clearing activity</a>. Thanks to xet7.</summary>
Describe clearing the card value instead of deleting the field definition.
Preserve both percent tokens and retain 25 correct rule/activity values.
Positive/negative regression and ledger checks pass; contextual rule grammar
and broader browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/87b9e45fb">Validate Galician rule actions and web metadata labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve checklist check/uncheck scope, wildcard
meaning, date actions and literal HTML/JSON configuration names. Review
checks pass; broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/44ff8c566">Validate Galician date activities and placement labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve authentication settings, date tokens,
placement directions and approaching/past/today deadline distinctions.
Review checks pass; broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/92b911aa9">Validate Galician reminders and notification controls</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve due conditions, exact reminder tokens,
read-state scope, account controls and weekday names. Review checks pass;
broader language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c78c7f3c5">Validate Galician entity and search predicate labels</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve entity names, view scope, date sorting
and ended/overdue/due predicate distinctions. Review checks pass; broader
language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/d2d90abf5">Validate Galician query syntax and troubleshooting instructions</a>. Thanks to xet7.</summary>
Retain 30 correct values. Preserve query shorthand, descending sort, period
predicates and executable diagnostic commands. Review checks pass; broader
language and browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/1adfb3bec">Validate Galician format instructions and request states</a>. Thanks to xet7.</summary>
Retain 27 correct values. Preserve template tokens, exact space entities,
spinner options and closed/resolved distinctions. Review checks pass;
ticket orthography and broader browser validation remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c6c3b90be">Clarify Galician Node heap garbage overwriting and validate ticket spelling.</a>. Thanks to xet7.</summary>
The diagnostic describes overwriting garbage with a bit pattern. Three
Galician ticket labels retain dictionary-accepted spelling. Exact correction,
placeholder, key-order and unchanged-review checks pass.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/8fbc62075">Validate Galician memory statistics and storage controls.</a>. Thanks to xet7.</summary>
Retained 35 correct diagnostic, invitation, organization, attachment,
progress and time-summary labels after meaning and UI-context review.
Exact unchanged-review checks pass; broader language review remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f51c7439f">Validate Galician upload limits and accessibility settings.</a>. Thanks to xet7.</summary>
Retained 30 correct registration, visibility, upload/avatar, custom
translation, ISO week, support and accessibility labels. Exact review
checks pass; uncertain rule fragments and broader language review remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/cfc2480e5">Fix Galician Received captions and validate scheduled jobs.</a>. Thanks to xet7.</summary>
Two Received captions now agree with the feminine card noun. Retained
30 correct lockout, scheduler and diagnostic values. Exact correction,
placeholder and unchanged-review checks pass; browser review remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4e3802901">Validate Galician migration retry and backup labels.</a>. Thanks to xet7.</summary>
Retained 30 correct retry/resume, completion, backup, storage and migration
control values. Exact review checks pass; broader language review remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/9683042c4">Validate Galician S3 settings and migration outcomes.</a>. Thanks to xet7.</summary>
Retained 30 correct migration start/stop, S3/MinIO endpoint, credential,
region and storage labels. Technical identifiers remain intact. Exact
review checks pass; broader language and browser review remain open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/b71db72ab">Validate Galician storage migrations and progress labels.</a>. Thanks to xet7.</summary>
Retained 30 correct storage, permission, restore/repair migration and
progress labels. Scope qualifiers and technical identifiers remain intact.
Exact review checks pass; broader language/browser review remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/01fa30d1c">Fix Galician completed-subtask agreement and validate migration steps.</a>. Thanks to xet7.</summary>
The completed tooltip now agrees with the feminine subtask noun. Retained
29 correct migration-step, scheduling interval and monitoring labels.
Exact correction, placeholder and review checks pass; browser review remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/d2780f008">Validate Galician scheduler and migration threshold wording.</a>. Thanks to xet7.</summary>
Retained 30 correct queue, run, concurrency, monitoring and migration
threshold labels. Numeric ranges and pause/resume meanings remain intact.
Exact review checks pass; broader language/browser review remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/a5e111f91">Validate Galician repository and account messages.</a>. Thanks to xet7.</summary>
Retained 29 correct resource, repository and account values. Temporary
lockout instructions preserve cause and retry timing; Schedule remains
pending context review. Exact unchanged-review checks pass.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/641062f8c">Validate Basque activity messages and Galician session labels.</a>. Thanks to xet7.</summary>
Retained 26 correct Basque activity messages and two Galician labels.
Action polarity and underscore placeholders remain intact. Basque archive
wording and nine Galician context-dependent findings remain pending.
Exact review checks pass; broader language/browser review remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/629577951">Clarify Basque archive activity terminology.</a>. Thanks to xet7.</summary>
Four messages distinguish archive state from generic storage. Retained
26 correct activity, permission, JSON-copy and invitation values. Exact
correction, token and review checks pass; browser validation remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/e34b55d69">Validate Basque rule fragments and activity settings.</a>. Thanks to xet7.</summary>
Retained 30 correct webhook, subtask, rule, activity and settings values.
Conditional fragments, optional authentication, irreversible deletion and
literal HTML tags retain their meanings. Exact review checks pass;
broader language/browser validation remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/9910a2c13">Clarify Basque archived search status and validate instructions.</a>. Thanks to xet7.</summary>
Search help distinguishes archived/unarchived cards from generic storage.
Retained 29 correct search, label and troubleshooting values. Operator
examples, commands and interpolation tokens remain intact. Exact correction
and review checks pass; browser validation remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/ea16ef95e">Validate Basque reports and storage migration messages.</a>. Thanks to xet7.</summary>
Retained 30 correct report, team/organization, invitation, storage and
migration values. Deletion restrictions, outcome polarity and technical
examples remain intact. Exact review checks pass; broader review remains open.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/a076987c9">Validate Basque date activities and webhook labels.</a>. Thanks to xet7.</summary>
Retained 28 correct date, upload, navigation, URL-scheme and webhook values.
Numeric ranges, outcomes and source tokens remain intact. Free and
impersonation terminology remain pending. Exact review checks pass.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4c788beef">Validate Basque date and standalone rule labels</a>. Thanks to xet7.</summary>
Retained 23 correct date, parent-card presentation, custom-field activity and
rule labels after inspecting their use and placeholders. Compound trigger
phrases and remaining translation findings are still under review.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/ffde8e84e">Validate Basque rule actions and date-field labels</a>. Thanks to xet7.</summary>
Retained 17 correct action and date-field labels. Shared conditional trigger
composition still requires repair; the audit records its Spanish auxiliary.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/2a22a42a9">Correct Basque rule archive terminology</a>. Thanks to xet7.</summary>
Archive and restore rule labels now refer to the archive instead of generic
storage. Regression checks preserve both directions. Shared trigger auxiliary
and composed grammar remain under review in the translation audit.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v11.77 2026-09-14 WeKan ® release
**In short:** Tamazight rules, search, email, attachments and maintenance
+11 -13
View File
@@ -555,19 +555,17 @@ directly after the merge.
`<details>` blocks below carry that information. Keep the paragraph current as
topics change, and shorten it when added commits make it grow. A finished release
keeps the paragraph it was written with.
- **No release section carries a Platform/Binary/From/Version/SHA256 table, and
none ever should again.** It used to sit right under the `**In short:**`
paragraph, then moved to a `**Binaries in these bundles:**` label at the end
of the section — both tried and both removed, because CHANGELOG.md is not
where that data belongs: it made every release's entry mostly a giant table
nobody read, on top of what `<details>` entries already say. "Which Node.js
is in the arm64 bundle of 10.69, and was it checked" is answered by the
**GitHub Release notes** instead: `releases/provenance-table.sh` puts the
same table at the TOP of those, built fresh from the `provenance.tsv` rows
each build job records (see `releases/record-provenance.sh`), every time a
release is made — so it is never stale and never needs hand-editing into
CHANGELOG.md. Adding a binaries table back into a CHANGELOG entry, in any
shape, is reintroducing something the maintainer removed on purpose.
- **Neither CHANGELOG.md nor release notes may contain a binaries provenance
section or a Platform/Bundle/Binary/From/Version/Checked/SHA256 table.**
Do not add "Binaries in these bundles" at the top or bottom of either.
Release notes summarize each Translations group as only a list of updated
languages. Add `**Languages updated:**` with comma-separated full language
names below the group label; keep detailed entries in the changelog and audit.
Release notes have only In short, Security, Translations (language list),
the standard thanks line and More details at ChangeLog with the release
anchor link. Other details stay in CHANGELOG.md. Keep download
verification and `provenance.tsv` build artifacts for diagnostics; do not
append their contents or `releases/provenance-table.sh` output to notes.
- **Inside a subsection, entries are GROUPED BY TOPIC/AREA.** A release touches a handful
of areas and repeating the area's name in every summary is the noise this
removes — twelve entries that each begin "All Boards:" say "All Boards" twelve
+1 -1
View File
@@ -20,7 +20,7 @@ LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
# TARGETARCH and TARGETVARIANT are automatically provided by Docker Buildx
ARG TARGETARCH
ARG TARGETVARIANT
ARG VERSION=11.77
ARG VERSION=11.78
ARG DEBIAN_FRONTEND=noninteractive
ENV BUILD_DEPS="apt-utils gnupg wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v11.77.0"
appVersion: "v11.78.0"
files:
userUploads:
- README.md
+2
View File
@@ -246,6 +246,8 @@ goto menu_releases
REM ---------------------------------------------------------------------------
:rel_release
REM Release notes: In short, Security, translation languages, thanks and changelog link.
REM Binary provenance stays in build artifacts.
echo.
echo -- Releases / Release -- ^(0 = Back^)
echo 1^) Release ALL platforms: push CHANGELOG, trigger release-all.yml
+58 -22
View File
@@ -2237,11 +2237,45 @@ Thanks to xet7 !"
return 0
}
# git pull: fast-forward when that is all it takes, rebase when the branch has
# diverged, and never end half-way through either.
# git pull: fast-forward or merge origin without rewriting local commit hashes.
# Preserve actual content conflicts for explicit resolution.
# Recover only Git's incomplete autostash-only setup, never an active rebase.
function git_recover_orphan_autostash(){
local directory stash
directory="$(git rev-parse --git-path rebase-merge)"
[ -d "$directory" ] || return 0
[ -f "$directory/autostash" ] || return 0
[ "$(find "$directory" -mindepth 1 -maxdepth 1 | wc -l)" -eq 1 ] || return 0
[ ! -e "$(git rev-parse --git-path index.lock)" ] || return 0
if ps -eo args | grep -E '^git (rebase|merge|stash|commit)( |$)' >/dev/null; then
echo "ERROR: another Git operation is running; recovery deferred."; return 1
fi
stash="$(cat "$directory/autostash")"
git cat-file -e "$stash^{commit}" || return 1
git stash store -m 'Recovered build.sh interrupted pull autostash' "$stash" || return 1
rm -- "$directory/autostash" && rmdir -- "$directory" || return 1
echo "==> Preserved interrupted pull autostash in git stash list."
}
function git_operation_ready(){
local state
for state in rebase-merge rebase-apply MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD index.lock; do
if [ -e "$(git rev-parse --git-path "$state")" ]; then
echo "ERROR: existing Git operation or lock: $state. Resolve it before pulling or pushing."
echo " Run git status; do not delete a lock while another Git process is running."
return 1
fi
done
[ "$(git rev-parse --abbrev-ref HEAD)" != HEAD ] || {
echo "ERROR: detached HEAD; select a branch first."; return 1;
}
}
function git_pull(){
git rev-parse --git-dir >/dev/null 2>&1 || { echo "Not a git repository."; return 1; }
local branch upstream before ahead behind
git_recover_orphan_autostash || return 1
git_operation_ready || return 1
branch="$(git rev-parse --abbrev-ref HEAD)"
upstream="origin/$branch"
echo "== git pull - branch $branch =="
@@ -2269,23 +2303,15 @@ function git_pull(){
# Nothing of ours to replay: a fast-forward moves the branch pointer and
# rewrites no commit, so no link can go stale.
echo "--- fast-forward (no local commits to replay) ---"
git merge --ff-only "$upstream" || { echo "ERROR: fast-forward failed. Nothing changed."; return 1; }
git -c merge.autoStash=true merge --ff-only "$upstream" || { echo "ERROR: fast-forward failed. Nothing changed."; return 1; }
else
echo "--- rebase: replaying $ahead local commit(s) onto $upstream ---"
echo " This gives them NEW hashes, which is why the CHANGELOG links are"
echo " repaired straight after."
if ! git -c rebase.autoStash=true rebase "$upstream"; then
git rebase --abort 2>/dev/null
git stash list >/dev/null 2>&1
echo
echo "ERROR: the rebase hit a conflict, so it was ABORTED - this repo is"
echo " exactly as it was before ($(git rev-parse --short "$before"))."
echo " Resolve it by hand:"
echo " git rebase $upstream # then fix the conflicts"
echo " git rebase --continue # or: git rebase --abort"
echo " and run this option again afterwards."
return 1
fi
echo "--- merge: integrating $upstream while preserving local commit hashes ---"
if ! git -c merge.autoStash=true merge --no-edit "$upstream"; then
echo "ERROR: merge did not finish. Your edits and conflict state are preserved."
git status --short
echo "Resolve the listed conflicts, git add the resolved files, then git merge --continue."
return 1
fi
fi
git_fix_changelog_links || return 1
@@ -2299,6 +2325,8 @@ function git_pull(){
function git_push(){
git rev-parse --git-dir >/dev/null 2>&1 || { echo "Not a git repository."; return 1; }
local branch ahead
git_recover_orphan_autostash || return 1
git_operation_ready || return 1
branch="$(git rev-parse --abbrev-ref HEAD)"
echo "== git push - branch $branch =="
@@ -2311,7 +2339,11 @@ function git_push(){
# everyone who reads the release notes.
git_fix_changelog_links || return 1
git fetch origin "$branch" >/dev/null 2>&1 || true
git fetch origin "$branch" || { echo "ERROR: fetch failed; nothing pushed."; return 1; }
if git rev-parse --verify --quiet "origin/$branch" >/dev/null &&
[ "$(git rev-list --count HEAD.."origin/$branch")" -gt 0 ]; then
git_pull || { echo "ERROR: pull failed; nothing pushed."; return 1; }
fi
if git rev-parse --verify --quiet "origin/$branch" >/dev/null; then
ahead="$(git rev-list --count "origin/$branch"..HEAD)"
[ "$ahead" -eq 0 ] && { echo "==> Nothing to push; origin/$branch is already at this commit."; return 0; }
@@ -2400,6 +2432,10 @@ choose() {
# (a helper other scripts source), ferretdb/* (they run INSIDE the built
# 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,
# 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 platforms: push CHANGELOG, trigger release-all.yml|releases/release-all.sh|||"
"Release|Release (older local flow), for one version|releases/release.sh|WeKan version, e.g. 10.50||"
"Release|Show the version numbers this checkout would release|releases/version.sh|||"
@@ -2469,7 +2505,7 @@ RELEASE_SCRIPTS=( "Release|Release ALL platforms: push CHANGELOG, trigger releas
"Translations|Report English strings that regressed|releases/translations/report-english-regressions.mjs|||"
"Translations|Prove a pull keeps human translations (no network)|releases/translations/verify-human-preference.mjs|||"
"Translations|Merge a finished pull by hand|releases/translations/merge-translations.mjs|||"
"Git and repo|git pull - fetch, fast-forward or rebase, repoint moved CHANGELOG links|!git_pull|||git-pull"
"Git and repo|git pull - fetch, fast-forward or merge, repoint moved CHANGELOG links|!git_pull|||git-pull"
"Git and repo|git push - verify the CHANGELOG links, push, pull-and-retry once if origin moved|!git_push|||git-push"
"Git and repo|Commit with the editor open for a multi-line message|releases/commit.sh|||"
"Git and repo|Add everything, then revert it again|!git restore --staged|Path to unstage||git-add-revert"
@@ -2681,7 +2717,7 @@ while [ -z "$opt" ]; do
"Install dependencies|Install WeKan dependencies" \
"Build WeKan release bundle|Build WeKan release bundle" \
"Build WeKan development bundle|Build WeKan development bundle" \
"git pull|git pull: fetch, fast-forward or rebase onto origin, repoint the CHANGELOG commit links the rebase moved, and leave the repo unchanged if anything conflicts" \
"git pull|git pull: fetch, fast-forward or merge onto origin, repoint the CHANGELOG commit links the rebase moved, and preserve unresolved conflict state" \
"git push|git push: check the CHANGELOG commit links resolve before publishing them, push this branch to origin, and pull-then-retry once if origin moved meanwhile" ;;
"Dev server")
choose "Dev server" \
@@ -2867,7 +2903,7 @@ for _once in 1; do
break
;;
"git pull: fetch, fast-forward or rebase onto origin, repoint the CHANGELOG commit links the rebase moved, and leave the repo unchanged if anything conflicts")
"git pull: fetch, fast-forward or merge onto origin, repoint the CHANGELOG commit links the rebase moved, and preserve unresolved conflict state")
git_pull
break
;;
+4 -1
View File
@@ -331,7 +331,10 @@ Template.activity.helpers({
},
activityMessage(key, ...values) {
const setting = ReactiveCache.getCurrentSetting();
return sanitizeHTML(TAPi18n.__(key, ...values), {
// Spacebars appends helper options; only rendered values belong in sprintf.
const lastValue = values[values.length - 1];
if (lastValue && typeof lastValue === 'object' && 'hash' in lastValue) values.pop();
return sanitizeHTML(TAPi18n.__(key, { sprintf: values }), {
stripLinks: !!(setting && setting.renderLinksAsPlainText),
});
},
@@ -2,7 +2,7 @@
- export buttons, then a Chart.js canvas, then the full data table, reusing
statsView.css's .stats-view/.stats-view-content shell. */
.chart-view-content {
max-width: 1100px;
max-width: none;
}
.chart-export-buttons {
+4 -2
View File
@@ -9,8 +9,10 @@
}
.stats-view-content {
max-width: 900px;
margin: 0 auto;
width: 100%;
max-width: none;
box-sizing: border-box;
margin: 0;
padding: 24px;
}
+7 -3
View File
@@ -142,10 +142,14 @@ export function translatedViewModes(GanttLib) {
function translateFrappeChrome(container, templateInstance) {
const apply = () => {
const today = container.querySelector('.today-button');
if (today && today.textContent === 'Today') today.textContent = TAPi18n.__('today');
const todayLabel = TAPi18n.__('today');
if (today && today.textContent === 'Today' && today.textContent !== todayLabel) {
today.textContent = todayLabel;
}
const placeholder = container.querySelector('.viewmode-select option[disabled]');
if (placeholder && placeholder.textContent === 'Mode') {
placeholder.textContent = TAPi18n.__('gantt-view-mode');
const modeLabel = TAPi18n.__('gantt-view-mode');
if (placeholder && placeholder.textContent === 'Mode' && placeholder.textContent !== modeLabel) {
placeholder.textContent = modeLabel;
}
};
if (!templateInstance.chromeObserver) {
+33 -8
View File
@@ -237,7 +237,7 @@
/* Auto-filling columns so as many languages as possible are visible at once when
there is browser width; collapses to a single column on narrow windows. */
display: grid;
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
align-content: start;
column-gap: 4px;
}
@@ -389,12 +389,11 @@
position: fixed !important;
bottom: 0 !important;
top: auto !important;
inset-inline-start: auto !important;
inset-inline-end: 20px !important;
/* Wide enough for multiple language columns when the browser is wide, but never
wider than ~90% of the viewport (falls back to a single narrow column). */
width: min(90vw, 1100px) !important;
max-width: 90vw !important;
inset-inline-start: 0 !important;
inset-inline-end: 0 !important;
box-sizing: border-box;
width: 100vw !important;
max-width: 100vw !important;
height: 100vh !important;
max-height: 100vh !important;
min-height: 300px !important;
@@ -1295,10 +1294,36 @@ body.mobile-mode .pop-over .content-wrapper {
}
.pop-over[data-popup='changeLanguagePopup'] .language-label {
display: inline-flex;
display: flex;
flex-direction: column;
gap: 0.15em;
min-width: 0;
width: 100%;
}
.pop-over[data-popup='changeLanguagePopup'] .language-main-line {
display: flex;
align-items: baseline;
gap: 0.25em;
}
.pop-over[data-popup='changeLanguagePopup'] .pop-over-list li > a {
display: flex;
align-items: flex-start;
white-space: normal;
overflow: visible;
text-overflow: clip;
height: auto;
}
.pop-over[data-popup='changeLanguagePopup'] .pop-over-list li,
.pop-over[data-popup='changeLanguagePopup'] .language-name {
min-width: 0;
overflow-wrap: anywhere;
}
.pop-over[data-popup='changeLanguagePopup'] .language-region {
display: block;
white-space: normal;
overflow-wrap: anywhere;
}
.pop-over[data-popup='changeLanguagePopup'] .language-region {
unicode-bidi: isolate;
}
+3 -2
View File
@@ -244,8 +244,9 @@ template(name="changeLanguagePopup")
li(class="{{# if isCurrentLanguage}}active{{/if}}")
a.js-set-language(href="#" dir="{{languageDirection}}" data-language="{{tag}}")
span.language-label
span.emoji-icon.language-flags(dir="ltr" aria-hidden="true") {{languageFlag}}
bdi.language-name {{languageName}}
span.language-main-line
span.emoji-icon.language-flags(dir="ltr" aria-hidden="true") {{languageFlag}}
bdi.language-name {{languageName}}
if countryFlag
span.language-region
| (
@@ -31,13 +31,14 @@ pins.
</details>
```
**No Platform/Binary/From/Version/SHA256 table belongs here, in any shape.**
It was tried twice - right under the summary, then moved to a `**Binaries in
these bundles:**` label at the end - and removed both times: it made the
entry mostly a giant table nobody read. That data lives in the **GitHub
Release notes** instead, built fresh by `releases/provenance-table.sh` from
`releases/record-provenance.sh`'s recordings every time a release is made -
see CLAUDE.md's/AGENTS.md's CHANGELOG section.
**Do not add a binaries provenance section or table to the changelog or
release notes.** Release notes list only updated languages for each Translations group.
Add `**Languages updated:** Esperanto, Galician` below that group label
with the actual affected full language names. Detailed entries stay here;
release notes include only In short, Security, translation languages,
the standard thanks line and a More details at ChangeLog release anchor link.
Keep checksum verification and `provenance.tsv` build artifacts separately;
never append `releases/provenance-table.sh` output to release notes.
## Why this exists instead of an auto-created empty section
+18 -8
View File
@@ -1,6 +1,6 @@
# LDAP bind investigation — issue 6692
Reviewed 2026-09-14. The issue and its empty comments API were read.
Reviewed 2026-09-14. The complete issue and all three current comments were read.
Snap 11.76 on Ubuntu reports a service-account LDAPS bind failure;
ldapsearch succeeds and reverting to 11.72 did not help.
@@ -10,10 +10,20 @@ retain name, message, code and reason through existing secret redaction.
Certificate and invalid-credential tests pass; encryption, connection-release
and REST login suites also pass. TLS verification remains enabled.
The actual bind cause is unresolved. Current code accepts the deprecated
ssl setting as LDAPS and disables strict DN parsing for UPN binds. The
supplied error contains neither certificate nor LDAP result details, so no
TLS downgrade, credential rewrite or speculative dependency change was made.
No external directory or live browser login was run. Obtain the repaired
server diagnostic before deciding whether this is TLS trust, connectivity
or LDAP authentication; do not include passwords or private settings.
The reporter's new 11.77 diagnostic identifies a TypeError reading index 0.
Local commit `2c5cdd78a` fixes the matching post-bind failure: an unset
optional LDAP_USER_SEARCH_FILTER was indexed in getUserFilter before the
user search. Undefined/null/empty now omit only the optional filter;
configured restrictions remain active and invalid types fail explicitly.
The reproduction and encryption, connection-release and error-logging suites
pass. No live external directory or browser login was available; deployment
confirmation remains necessary. TLS verification remains unchanged.
Latest comment still uses 11.77, before the local post-bind fix. Regression
commit `dad65fb6c` exercises actual bindIfNecessary/searchUsers methods,
verifying successful UPN service binding reaches the correct user query,
configured filters remain active, and invalid credentials stop before search.
A separate oplog observer duplicate-insert error is reported one second later;
its connection to login is unproven and no database repair was guessed.
Deploy a release containing `2c5cdd78a` and confirm the LDAP login separately
from that observer error. No live directory or browser login was run.
@@ -21741,3 +21741,738 @@ accessibility and account labels retained, including login-only access,
not-yet-added information and only-locked-user filtering. All 18,151
corrections and 1,981 reviews pass. 2,237 restored values and 308 pending
findings plus broader language/browser verification remain open.
### Esperanto scheduled-job and migration review — 2026-09-14
Local commit `5946767b1` retains twenty-nine correct scheduled-job,
migration-error and account-unlock labels. Resume/start, failed/paused and
success/failure outcomes remain distinct. No-failed/no-paused negation and
clear-all-errors scope are preserved. Isolated Complete remains ambiguous
between an action and state and is not accepted merely by vocabulary match.
2,010 review records pass; 2,208 restored values and 308 pending findings
plus broader language/browser verification remain open.
### Esperanto storage and migration settings — 2026-09-14
Local commit `460df4ada` retains thirty correct migration actions/progress,
S3 storage/connection labels and integrity descriptions. Empty duplicate
list deletion requires no cards AND a same-title counterpart with cards;
Esperanto KAJ preserves both conditions. Start/pause/stop and connection
success/failure remain distinct. All 2,040 review records pass; 2,178
restored values and 308 pending findings plus wider language/browser
verification remain open.
### Esperanto migration repair descriptions — 2026-09-14
Local commit `036cbff31` retains nineteen correct restoration, URL-repair,
data-integrity and migration outcome/permission values. Field names
swimlaneId/listId stay literal; all archived object types, this-board scope,
individual execution and board-admin-only restriction remain accurate.
Complete and migration-complete remain unvalidated: no current literal
consumer was found for the latter, so its state/action meaning is uncertain.
All 2,059 reviews pass. 2,159 restored values and 308 pending findings plus
broader language/browser verification remain open.
### Esperanto migration confirmations and steps — 2026-09-14
Local commit `07756eecd` retains thirty correct confirmations and progress
steps. Shared-list conversion precedes empty duplicate deletion; lost-card
restoration affects nonarchived items, whereas archive restoration affects
all archived types. Difficult-undo warning, field identifiers and this-board
URL scope remain explicit. All 2,089 reviews pass; 2,129 restored values and
308 pending findings plus broader language/browser verification remain open.
### Esperanto scheduling intervals and diagnostics — 2026-09-14
Local commit `5a398d5ac` retains thirty correct restoration/scanning,
completion, resource and interval labels. Ĉiun/Ĉiujn agrees with singular
and plural day/hour/minute quantities. CPU percentage, run-once scope and
previous/next order remain intact. All 2,119 reviews pass; 2,099 restored
values and 308 pending findings plus broader language/browser verification
remain open.
### Esperanto repository and authentication review — 2026-09-14
Local commit `9ba7cfcb8` retains thirty correct repository, storage/minicard
and authentication values. Temporary lockout from excessive failed logins,
username AND password requirements, minimum three characters and byte units
remain explicit. Cron/OTP/API/S3 names stay literal. All 2,149 reviews pass;
2,069 restored values and 308 pending findings plus wider language/browser
verification remain open.
### Esperanto remaining basic labels — 2026-09-14
Local commit `f523001c3` retains ten correct account-outcome and basic
labels, preserving success/failure and sign-in/sign-out distinctions.
Nine Esperanto restored findings remain uncertain: overtime variants,
lime color, scrollbar, advanced filter, garbage zapping and two Complete
labels. These are not certified by this basic-label batch. All 2,159 reviews
pass; globally 2,059 restored and 308 pending findings plus broader
language/browser verification remain open.
### Esperanto heap garbage-zapping — 2026-09-14
Local commit `05217bbb9` replaces nuligas with superskribas rubon per
bitŝablono. Official https://nodejs.org/api/v8.html#v8getheapstatistics
specifies overwrite with a bit pattern, not necessarily zeroing. This
resolves the earlier diagnostic uncertainty. All 18,152 correction records
pass. Eight Esperanto restored findings remain; globally 2,058 restored
and 308 pending findings plus broader language/browser review stay open.
### Esperanto scrollbar terminology — 2026-09-14
Local commit `41fb26dff` retains Ŝalti vertikalajn rulumskalojn. Primary
references https://komputeko.net/Komputeko2012.pdf and
https://help.libreoffice.org/latest/eo/text/shared/02/01170000.html confirm
rulumskalo as scrollbar, resolving the earlier terminology suspicion.
Lime remains uncertain: document renderers use #00ff00 while contrastColor
uses #51e898, so a single exact hue cannot be inferred from the token alone.
2,160 review records pass; seven Esperanto restored findings remain.
Globally 2,057 restored and 308 pending findings and wider language/browser
validation remain open.
### Esperanto advanced-filter repair — 2026-09-14
Local commit `5d94f1b92` preserves canonical Field1/Value1 and quoted
examples while retaining Esperanto prose. The tokenizer escapes punctuation
as literal characters; help now says specialajn signojn ... literalajn
signojn rather than skipping control characters. Operators, regex and
backslash inventory remain exact. Focused positive examples and negative
wording coverage pass; all 18,153 corrections pass. Six Esperanto restored
findings remain. Globally 2,056 restored and 308 pending findings plus wider
language/browser verification remain open.
### Esperanto overtime-work wording — 2026-09-14
Local commit `9b6b1ffe6` uses Kromlaboro consistently in three overtime
labels. cardTime.js stores the manually selected isOvertime alongside
spentTime; this is work classification, not computed deadline expiry or
extra time granted. Hours unit remains intact. Reference comparison:
https://www.learnentry.com/english-esperanto/meaning/overtime/ gives
kromlaboro; https://retavortaro.de/revo/art/labor.html establishes the
compound for additional work, though its example is unofficial side work,
not a payroll-specific definition. Thus native full-phrase fluency still
belongs to the broader review. All 18,156 corrections pass. Three Esperanto
restored findings remain; globally 2,053 restored and 308 pending findings
plus broader language/browser verification remain open.
### Galician card and checklist activities — 2026-09-14
Local commit `0d2e977d9` retains twenty-five correct attachment, label,
checklist, comment, creation and custom-field actions. Galician past verbs
and marcado/desmarcado distinctions preserve their actions; object
relationships and placeholders remain intact. All 2,185 reviews pass;
2,028 restored values and 308 pending findings plus broader language and
browser verification remain open. Three uncertain Esperanto findings remain.
### Galician membership and movement activities — 2026-09-14
Local commit `b82f539a4` retains twenty-five correct membership,
archive/import/restore, card-movement and checklist activity values.
Old/new lists, lanes and boards retain source/destination direction;
checked/unchecked and complete/reopened states remain distinct. Exact
interpolation retained. All 2,210 reviews pass; 2,003 restored values and
308 pending findings plus wider language/browser verification remain open.
### Galician creation controls and warnings — 2026-09-14
Local commit `f1add7ab2` retains twenty-five correct creation, placement,
member-setting/privilege, count and archive values. Offline warning
preserves refresh-causes-data-loss and server-not-stopped checks. Counts
retain singular/plural agreement and interpolation. All 2,235 review records
pass; 1,978 restored values and 308 pending findings plus broader language
and browser verification remain open.
### Galician board settings and archive controls — 2026-09-14
Local commit `68277a708` retains twenty-five correct archive, background,
board-info, member/assignee and privacy controls. All-card versus current-card
scope, public/private distinction, HTML emphasis and archive-empty negation
remain intact. All 2,260 review records pass; 1,953 restored values and
308 pending findings plus wider language/browser verification remain open.
### Galician deletion and archive guidance — 2026-09-14
Local commit `4c585a2b8` retains twenty-five correct board-view,
deletion/archive, membership and card-type values. Permanent deletion,
activity loss and inability to undo/reopen remain explicit, distinct from
reversible archival and restore guidance. All 2,285 review records pass;
1,928 restored values and 308 pending findings plus broader language/browser
verification remain open.
### Galician role descriptions and card controls — 2026-09-14
Local commit `96367d213` retains twenty-five correct favorite, close,
comment/role, deletion and link/copy values. Assigned-only visibility,
no-edit and no-comment-visibility restrictions, worker self-assignment and
favorite/unfavorite opposites remain explicit. All 2,310 reviews pass;
1,903 restored values and 308 pending findings plus broader language/browser
verification remain open.
### Galician import, template and export review — 2026-09-14
Local commit `316232f69` restores literal source Trello menu labels in
Galician import help, matching existing audited navigation policy. Galician
prose remains; 29 template, invitation, role-error, export and filter values
retained. JSON title/description properties, placeholders, permanent
custom-field deletion and free-disk-space warning remain exact. All 18,157
corrections and 2,339 reviews pass. 1,873 restored values and 308 pending
findings plus wider language/browser verification remain open.
### Galician import mapping and privacy — 2026-09-14
Local commit `7f7db419c` retains twenty-five correct import/member,
list-action, notification and privacy values. Unmapped members map to the
current user; at least one administrator remains required. Assigned-only
visibility, creator/member participation and public-view/member-only-edit
qualifiers remain exact. All 2,364 reviews pass; 1,848 restored values and
308 pending findings plus wider language/browser verification remain open.
### Galician removal and card shortcuts — 2026-09-14
Local commit `4f0293f5c` retains twenty-five correct removal, rescue,
shortcut, favorite and time-indicator values. Singular member notification,
all-card removal effects, unsaved-description overwrite warning and
self-membership/assignment remain distinct. More-than threshold and 1-9
ordering are preserved. All 2,389 reviews pass; 1,823 restored values and
308 pending findings plus wider language/browser verification remain open.
### Galician visibility and deletion warnings — 2026-09-14
Local commit `e74cc6464` retains twenty-five correct visibility,
invitation, field-application and deletion-warning values. Private-only,
new/all-card scope, invitation placeholders and no-undo warnings remain
explicit. Duplicate-list deletion retains same-name AND empty conditions.
All 2,414 reviews pass; 1,798 restored values and 308 pending findings plus
broader language/browser verification remain open.
### 2026-09-14 — Galician card settings and automation review
Local commit `edea4b20e` retains 25 directly reviewed values unchanged.
Card/minicard settings preserve parent relationships, subtask destinations,
attachment counts and source-board meaning. Activity verbs retain exact
percent placeholders; automation controls preserve add-action/add-trigger
and move-all scope. Review ledger checks pass.
Tracked status: 15,561 corrected, 1,773 restored awaiting validation,
2,439 reviewed unchanged and 308 pending. Broader language, uncertain
terminology and browser verification remain open; nothing pushed.
### 2026-09-14 — Galician automation date triggers and assembled grammar
Local commit `54fdfd33c` repairs four unflagged date-trigger values:
due/start/end/received conditions now cover both setting and changing dates,
replacing Portuguese início/Fim/Recebido wording and incomplete meanings.
Retained 39 directly inspected automation, HTML-placement and duplicate-board
values unchanged. Positive/negative date tests and all ledger checks pass.
Actual cardTriggers.jade attachment control combines masculine anexo with
shared feminine r-added-to/r-removed-from labels; member/assignee controls
share these labels too. Leave r-when-a-attach unaccepted pending contextual
agreement repair rather than certify the assembled sentence as correct.
Tracked: 15,561 corrected, 1,734 restored awaiting validation, 2,478 reviewed
unchanged and 308 pending; correction ledger 18,161. Broader grammar and
browser verification remain open. No external translation service or push.
### 2026-09-14 — Galician deletion warnings and personal card views
Local commit `2cb3f6b13` retains 34 directly reviewed values unchanged.
Warnings preserve irreversible deletion and linked-card removal order.
Mention text preserves every exact interpolation token; due-card help keeps
incomplete-card and user-permission restrictions and markdown emphasis.
Personal/due-card view, sorting, unread and visibility labels retain meaning.
Review checks pass: 15,561 corrected, 1,700 restored awaiting validation,
2,512 reviewed unchanged and 308 pending. Attachment/member participle
agreement, broader language review and browser validation remain open.
Nothing pushed; no external translation service used.
### 2026-09-14 — Galician search results and operator instructions
Local commit `cf1e89bb9` retains 40 directly reviewed values unchanged.
Search errors retain object names and format tokens; result counts preserve
start/end/total. Help retains literal query syntax and quotation examples,
member-or-assignee alternatives, creator and organization/team scopes,
created/modified day thresholds and archived/end-date distinctions.
Review checks pass: 15,561 corrected, 1,660 restored awaiting validation,
2,552 reviewed unchanged and 308 pending. Contextual rule agreement,
broader language review and browser validation remain open; nothing pushed.
### 2026-09-14 — Galician search predicates and checklist instructions
Local commit `f22aeb7bc` retains 39 directly reviewed values unchanged.
Search help preserves absence, positive per-page limits, OR/AND logic,
case insensitivity, archive defaults and literal examples. Deletion warnings
retain at-least-one membership constraints. Checklist copy/move and
one-line-per-item original order remain distinct. Legal acceptance noso
agrees with aviso legal in layouts.jade. Card-show-lists remains unaccepted
pending field-context inspection; attachment grammar remains open.
Review checks pass: 15,561 corrected, 1,621 restored awaiting validation,
2,591 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician storage and account-lockout labels
Local commit `2f141de75` retains 30 directly reviewed values unchanged.
Storage help distinguishes all attachments from board-only scope and retains
GridFS/S3/ZIP/JSON names. PDF fallback, irreversible custom-string deletion,
support visibility and known/unknown/locked-account distinctions are intact.
Card-show-lists context is resolved: cardSettingsRows.js showLists row and
sidebar allowsShowLists toggle show that its declarative visibility wording
is appropriate; retained unchanged. Attachment grammar remains open.
Review checks pass: 15,561 corrected, 1,591 restored awaiting validation,
2,621 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician account states and scheduled board actions
Local commit `3b524f071` retains 29 directly reviewed values unchanged.
Unlock confirmations preserve this-user/all-users and only-locked scope;
activation controls preserve active/inactive inversion. Archive, backup and
cleanup messages distinguish successful scheduling from failed scheduling,
without claiming execution has completed. Storage-path and job labels retain
meaning. Complete remains unaccepted pending action/status context.
Review checks pass: 15,561 corrected, 1,562 restored awaiting validation,
2,650 reviewed unchanged and 308 pending. Contextual agreement, broader
language and browser validation remain open. Nothing pushed.
### 2026-09-14 — Galician migration scopes and confirmations
Local commit `ff77543b5` retains 34 directly reviewed values unchanged.
Duplicate-list repair preserves empty AND same-title populated counterpart
and conversion-before-deletion order. Lost-card restore excludes archived
items; restore-all includes them and preserves the undo warning. Missing
IDs, administrator-only and individual execution restrictions, URL storage
references and progress steps retain meaning. Completion label remains open.
Review checks pass: 15,561 corrected, 1,528 restored awaiting validation,
2,684 reviewed unchanged and 308 pending. Broader grammar, terminology and
browser validation remain open. Nothing pushed.
### 2026-09-14 — Galician migration execution and account messages
Local commit `e8766c17b` retains 30 directly reviewed values unchanged.
Migration text preserves once-per-board conversion, background continuation
when closing the browser, longer completion warning, 1-100 batch limits
and migrated/remaining/total attachment states. Authentication keeps invalid
credential alternatives, both required inputs, password mismatch and at-least
three-character username. Activity tokens and visibility labels remain intact.
Review checks pass: 15,561 corrected, 1,498 restored awaiting validation,
2,714 reviewed unchanged and 308 pending. Broader grammar, terminology and
browser validation remain open. Nothing pushed.
### 2026-09-14 — Galician activities and workspace controls
Local commit `4b926a60f` retains 35 directly reviewed values unchanged.
Activities preserve move/import directions, join/leave actions, received,
start/due/end date distinctions and exact percent tokens. Workspace and
subworkspace controls, deletion confirmation, equal list width and swimlane
height preserve meaning. Keyboard shortcut labels correctly invert current
state and click action; markdown retains its actual format name.
Review checks pass: 15,561 corrected, 1,463 restored awaiting validation,
2,749 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician display settings and archive controls
Local commit `708478423` retains 30 directly reviewed values unchanged.
Height controls retain positive-integer and pixel constraints. Checklist edit
closure, active system-wide announcements, archive-all/list/swimlane/selection
scope, avatar size token, background URLs and opposing zoom directions are
intact. Mobile/desktop switching and visibility/watch settings retain meaning.
Review checks pass: 15,561 corrected, 1,433 restored awaiting validation,
2,779 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician calendar and voting labels
Local commit `e5e09f52c` retains 30 directly reviewed values unchanged.
Zoom range, calendar month directions, deadline/start/spent-time labels and
attachment origin retain meaning. Voting preserves proponents/opponents,
all logged-in eligibility, visible vote choices, end-date editing and
permanent loss of all associated actions. Wish-list example retains meaning.
Review checks pass: 15,561 corrected, 1,403 restored awaiting validation,
2,809 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician estimation controls and color labels
Local commit `47fb0f277` retains 30 directly reviewed values unchanged.
Planning Poker remains the named estimation practice; vote end-date,
replay/edit/delete and dependency/template controls preserve action meaning.
CAS, avatar confirmations and clipboard alternatives retain meaning.
Automatic-width labels invert current state and click action correctly.
Color labels preserve hue and pale distinctions, including verde lima.
Review checks pass: 15,561 corrected, 1,373 restored awaiting validation,
2,839 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician custom fields and access labels
Local commit `98c90fd8a` retains 30 directly reviewed values unchanged.
Access labels distinguish read-only from assigned-only; custom-field types,
currency code, none/unknown dropdown alternatives, action disambiguation,
soft WIP limit and siteName email interpolation retain meaning. Peach/brown
palette names identify their actual swatches (contrastColor.js); no competing
peach/brown names require speculative new qualifiers.
Review checks pass: 15,561 corrected, 1,343 restored awaiting validation,
2,869 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician account emails and export fields
Local commit `ac0f8696f` retains 29 directly reviewed values unchanged.
Enrollment/reset/verification emails preserve purpose, exact user/URL/site
and inviter tokens and paragraph layout. Sent/failed/invalid and taken-name
states remain distinct. Excel fields preserve people roles and attachment
filename/size/uploader/time. Scrollbars, WIP and sorting scope retain meaning.
Export free remains unaccepted pending context.
Review checks pass: 15,561 corrected, 1,314 restored awaiting validation,
2,898 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician filters and import mapping labels
Local commit `c45e99b02` retains 30 directly reviewed values unchanged.
Filters preserve manual order, absent/overdue and relative-date distinctions,
assignee absence, archived/empty-list visibility and activity show/hide.
Import help preserves CSV/TSV separators, valid JSON, ZIP size failure,
cancellation and member mapping to an existing user.
Review checks pass: 15,561 corrected, 1,284 restored awaiting validation,
2,928 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician selection and board-management labels
Local commit `885d65b3b` retains 30 directly reviewed values unchanged.
Selection color/label/member controls preserve selection scope; assigned-only
Normal preserves the named role restriction. Private-page login help retains
possibility, HTML link and exact href format token. Image paste help remains
image-only. Version, user/team/organization, list and swimlane labels retain
meaning and label-default preserves interpolation.
Review checks pass: 15,561 corrected, 1,254 restored awaiting validation,
2,958 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician shortcuts and custom branding labels
Local commit `64a60f189` retains 30 directly reviewed values unchanged.
WIP maximum tasks per list, hours units, labels 1-9 removal and shortcut
sidebar/dialog actions retain scope. Upload/ongoing/failure states remain
distinct. Custom logo image/link URLs, upper-left placement, default height
27, text-below position and one clickable URL scheme per line retain meaning.
Milestone example remains Fito 1.
Review checks pass: 15,561 corrected, 1,224 restored awaiting validation,
2,988 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician SMTP and system diagnostic labels
Local commit `6c534f40f` retains 30 directly reviewed values unchanged.
WIP warning preserves over-limit scope and either move-out or raise-limit
remedies. SMTP/TLS and test success retain purpose; inviter token is exact.
Webhook labels preserve optional authentication, two-way/global/outgoing
scope and disable action. System diagnostics preserve actual product names,
CPU count, free/total memory, load average and uptime distinctions.
Review checks pass: 15,561 corrected, 1,194 restored awaiting validation,
3,018 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician account permissions and subtask settings
Local commit `dda51a1e5` retains 28 directly reviewed values unchanged.
Account-change permissions, created/modified metadata, active team/org/person,
role attribution, selection color and all-notification scope retain meaning.
Subtask landing list, full-path/parent prefixes and full-path subtext retain
relationship scope. Received/received-on participles remain unaccepted pending
agreement and presentation-context inspection.
Review checks pass: 15,561 corrected, 1,166 restored awaiting validation,
3,046 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician custom-field value clearing
Local commit `29f1980b2` repairs activity-unset-customfield: models/cards.js
registers clearing a card value, not deleting the field definition. Both
percent tokens remain exact. Retain 25 directly reviewed movement, archive,
checklist and rule controls. Shared added/removed and member fragments remain
unaccepted pending contextual agreement repair.
Positive/negative regression and ledger checks pass: 15,562 corrected,
1,140 restored awaiting validation, 3,071 reviewed unchanged and 308 pending.
Correction ledger 18,162. Broader language/browser validation remains open.
Nothing pushed.
### 2026-09-14 — Galician rule actions and web metadata labels
Local commit `87b9e45fb` retains 30 directly reviewed values unchanged.
Email recipient/subject/message, all/one checklist check/uncheck and swimlane
scope retain meaning. Empty-field wildcard and current date/time actions
remain distinct. Authentication method/type and custom head/meta/link tags,
manifest JSON and literal assetlinks.json configuration names are intact.
Review checks pass: 15,562 corrected, 1,110 restored awaiting validation,
3,101 reviewed unchanged and 308 pending. Contextual agreement and broader
language/browser validation remain open. Nothing pushed.
### 2026-09-14 — Galician date activities and placement labels
Local commit `44ff8c566` retains 30 directly reviewed values unchanged.
Authentication display/default and OIDC controls, login failure, counts and
restore/delete-all retain meaning. Date activities preserve new/old tokens,
line breaks and received/start/end/due distinctions. Placement retains
above/below swimlanes and left/right lists; approaching/past/today deadlines
remain distinct.
Review checks pass: 15,562 corrected, 1,080 restored awaiting validation,
3,131 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician reminders and notification controls
Local commit `92b911aa9` retains 30 directly reviewed values unchanged.
First-reminder and approaching/past/now conditions preserve exact list/card,
board/time tokens. Self-delete, drag handles and assignee singular/plural/
absence retain meaning. Entity create/edit, all-read notification scope,
rename permission, week-start setting and weekdays remain correct.
Review checks pass: 15,562 corrected, 1,050 restored awaiting validation,
3,161 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician entity and search predicate labels
Local commit `c78c7f3c5` retains 30 directly reviewed values unchanged.
Owner/person/team/organization, display/short names and modified metadata
retain meaning. Checked-item hiding, task creation, table view, due-date
sorting and all-user scope remain correct. Search operators and predicates
retain role/object names and ended/overdue/due distinctions.
Review checks pass: 15,562 corrected, 1,020 restored awaiting validation,
3,191 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician query syntax and troubleshooting instructions
Local commit `d2d90abf5` retains 30 directly reviewed values unchanged.
Query shorthand, exact operator/value tokens, pagination, descending minus,
positive/negative day integers and current-period predicates remain intact.
Server troubleshooting preserves exact snap/docker commands. Alphabetic and
newest/oldest ordering remain distinct.
Review checks pass: 15,562 corrected, 990 restored awaiting validation,
3,221 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician format instructions and request states
Local commit `1adfb3bec` retains 27 directly reviewed values unchanged.
String templates retain literal %{value} and exact space entities &#32;/&nbsp;.
Copy/move and report labels retain scope. Spinner animation labels remain
meaningful in their selector. Email Cc/subject, closed/resolved/history and
request singular/plural remain distinct. Ticket orthography is unaccepted
pending dictionary review.
Review checks pass: 15,562 corrected, 963 restored awaiting validation,
3,248 reviewed unchanged and 308 pending. Broader language and browser
validation remain open. Nothing pushed.
### 2026-09-14 — Galician heap diagnostic and ticket spelling
Local commit `c6c3b90be` repairs Node_heap_does_zap_garbage: overwriting
heap garbage with a bit pattern is distinct from garbage collection.
Source: https://nodejs.org/download/release/latest-v16.x/docs/api/v8.html
Retained ticket, tickets and ticket-number after dictionary verification:
https://academia.gal/dicionario/-/termo/t%C3%ADcket
Correction and review ledger checks pass; full language/browser scope remains open.
### 2026-09-14 — Galician diagnostics and storage labels
Local commit `8fbc62075` retains 35 correct memory/context statistic,
invitation, organization, storage, attachment progress and time labels.
The diagnostics correspond to fields in client/components/settings/informationBody.jade;
attachment progress is a file counter, and timeView displays elapsed time.
Technical heap/malloc identifiers remain intact. Exact unchanged-review checks
pass. Ambiguous Complete/Received labels and shared rule agreement remain open.
### 2026-09-14 — Galician uploads and accessibility settings
Local commit `f51c7439f` retains 30 correct account, upload/avatar byte-limit,
file-type, cancellation, custom translation, ISO week and page-settings labels.
Context checked in cardDetails.jade, userAvatar.jade, sidebar.jade,
settingBody.jade and peopleBody.jade. No interpolation tokens changed.
Exact unchanged-review checks pass; full grammar/browser scope remains open.
### 2026-09-14 — Galician received-card agreement and scheduler labels
Local commit `cfc2480e5` changes card-received and card-received-on to
Recibida and Recibida o. cardDetails.jade/cardDate.js describe the feminine
tarxeta and its date. Thirty lockout/scheduler values retain correct meanings.
Exact positive/negative corrections and unchanged-review checks pass.
Browser rendering, shared rule agreement and full language review remain open.
### 2026-09-14 — Galician migration retries and backups
Local commit `4e3802901` retains 30 correct migration retry/resume messages,
backup completion/scheduling/listing, storage and migration control labels.
Success/failure/absence polarity and GridFS identifiers are preserved.
Exact unchanged-review checks pass; full language/browser scope remains open.
### 2026-09-14 — Galician S3 settings and migration outcomes
Local commit `9683042c4` retains 30 correct migration start/stop messages,
S3/MinIO access/secret-key, bucket, endpoint, port, region and storage values.
Settings form and required-secret alerts confirm the field context.
Literal product names, endpoint examples and us-east-1 are preserved.
Exact unchanged-review checks pass; full language/browser scope remains open.
### 2026-09-14 — Galician storage migrations and progress
Local commit `b71db72ab` retains 30 storage, writable-path, migration action,
restoration/repair and step-progress labels. Duplicate/empty/all qualifiers
and technical SSL/TLS, S3 and URL identifiers preserve the English meaning.
The progress component displays overall/current-step labels and step counts.
Exact unchanged-review checks pass; full language/browser scope remains open.
### 2026-09-14 — Galician completed subtask and migration steps
Local commit `01fa30d1c` corrects completed to Completada, matching feminine
subtarefa in client/components/cards/subtasks.jade's completed icon tooltip.
Twenty-nine migration-step, interval and monitoring labels retain correct
meaning, numeric intervals, missing/duplicate/empty scope and technical IDs.
Exact positive/negative correction and unchanged-review checks pass.
Full language/browser scope remains open.
### 2026-09-14 — Galician scheduler and migration thresholds
Local commit `d2780f008` retains 30 scheduler, monitoring, storage destination
and migration threshold values. attachmentSettings.jade confirms CPU pause
percentage and millisecond-delay fields. Numeric ranges 10-90 and 100-10000,
last/next execution, failure polarity and pause/resume/run-once meanings remain.
Exact unchanged-review checks pass; full language/browser scope remains open.
### 2026-09-14 — Galician repository and account labels
Local commit `a5e111f91` retains 29 resource, repository and account values,
including lockout cause, retry timing and required OTP. Schedule is pending
because its action/noun context is not established. Exact reviews pass.
Activity rendering repaired separately in `888e0e6bc`; formatting failures
were caller API misuse, not incorrect translation placeholders.
### 2026-09-14 — Basque activities and Galician file/session labels
Local commit `641062f8c` retains 26 Basque activity messages and two Galician
file/session labels. Basque add/remove, check/uncheck and complete/uncomplete
prose preserves source action meanings and all underscore tokens, including
checkList casing. Archive terminology is excluded pending verification.
Nine Galician restored findings remain. Exact unchanged reviews pass;
full language/browser scope remains open.
### 2026-09-14 — Basque archive activity terminology
Local commit `629577951` replaces biltegira with artxibora in four archived
board/card/list/swimlane activities, distinguishing archive state from storage.
Dictionary source: https://www.ehu.eus/eeh/cgi/bila?h=artxibo
Twenty-six activity, permission, JSON-copy, mapping and invitation values retain
correct meanings and canonical keys/tokens. Exact positive/negative correction
and unchanged-review checks pass; full grammar/browser scope remains open.
### 2026-09-14 — Basque rule fragments and activity settings
Local commit `e34b55d69` retains 30 webhook, subtask, activity, rule builder,
custom HTML insertion and account/settings values. Conditional denean follows
card/label/checklist subjects in the trigger builder. Optional authentication,
irreversible removal, first reminder and literal body tags retain meaning.
Exact unchanged-review checks pass; full grammar/browser scope remains open.
### 2026-09-14 — Basque archived search status and operators
Local commit `9910a2c13` repairs all-status archived/unarchived terminology,
using artxibatutako/artxibatu gabeko consistently with artxibo activity captions.
Twenty-nine user, label, operator and troubleshooting values retain meanings.
Canonical syntax, quoted examples, OR/AND, absence/sort/page instructions,
interpolation tokens and snap/docker commands remain exact.
Positive/negative correction and review checks pass; browser scope remains open.
### 2026-09-14 — Basque reports, teams and storage migration
Local commit `ea16ef95e` retains 30 correct report, team/organization,
invitation, item-entry, migration and storage configuration values.
Team deletion remains blocked while a user belongs to it; migration all-scope
and success/failure polarity are preserved. Literal endpoint examples,
product identifiers and percent tokens remain unchanged.
Exact reviews pass; full language/browser scope remains open.
### 2026-09-14 — Basque dates, uploads and webhooks
Local commit `a076987c9` retains 28 date/checklist activities, calendar/table
navigation, upload outcome, URL scheme, milestone and webhook/database values.
50-300%, one scheme per line, receipt-date meaning and source tokens remain.
Free and impersonation wording are excluded pending contextual review.
Exact unchanged reviews pass; full language/browser scope remains open.
**2026-09-14 — Basque impersonation review (`f29c14def`).**
Retained `impersonate-user`: Euskadi dictionary includes acting in another
person’s place in `ordezkatu`, matching the actual administrator handler.
Source: https://www.euskadi.eus/web01-a2lurhiz/es/contenidos/termino/_c04593/eu_o_1340/o1340.html
All 3,696 exact review records pass; broader repairs remain open.
**2026-09-14 — Basque dates and standalone rules (`4c788beef`).**
Retained 23 inspected date, requester/assigner, parent-card presentation,
custom-field activity and standalone rule labels. Actual minicard parentCardName
confirms the parent relationship; unset activities clear a value, not a definition.
All 3,719 exact reviews pass. Compound conditional trigger phrases remain open.
**2026-09-14 — Basque rule actions (`ffde8e84e`).**
Retained 17 inspected action and date-field labels; 3,736 exact reviews pass.
Found unflagged Spanish `es` in shared `r-is`. Actual board/card/checklist
triggers combine it with Basque conditional verbs already ending in `denean`.
A literal copula replacement alone would leave redundant composed grammar;
coordinated phrase repair is required before accepting these trigger values.
**2026-09-14 — Basque rule archive terminology (`2a22a42a9`).**
Corrected `r-archived` and `r-unarchived` from generic storage to archive
terminology, preserving into/from directions. Exact archive regression and
all 18,173 correction records pass, including token preservation and newer
translation protection. Spanish `r-is` and composed grammar remain open.
+23 -14
View File
@@ -4,24 +4,33 @@ Audit date: **2026-09-12**. Last updated: **2026-09-14**.
| Status | Flagged keys |
| --- | ---: |
| Corrected | 15,555 |
| Restored pre-pull; awaiting validation | 2,208 |
| Reviewed; retained unchanged | 2,010 |
| Corrected | 15,571 |
| Restored pre-pull; awaiting validation | 466 |
| Reviewed; retained unchanged | 3,736 |
| Pending review or repair | 308 |
| Total tracked | 20,081 |
Review is ongoing. The [correction ledger](../../../releases/translations/audited-corrections.json)
contain **18,151** exact before/after values, including unflagged repairs.
contain **18,173** exact before/after values, including unflagged repairs.
[Detailed evidence](Audit-Evidence.md) retains categorized findings,
source references, confidence limits and archived progress notes.
Latest translation fix: **2026-09-14**, local commit `6eaabcaea` —
clarified the login-failure time window in Esperanto; retained 29 correct
support, accessibility and account-state labels. All **18,151** correction
and **1,981** review records pass. Browser verification was not run; no
translations were pushed. Overtime, lime-color, scrollbar, advanced-filter
and garbage-zapping wording remains under review. Checklist completion was
validated against ReVo komplet (`5b1ec70e9`).
Latest translation fix: **2026-09-14**, local commit `2a22a42a9` —
corrected Basque rule archive/restore terminology, preserving direction.
All **18,173** correction and **3,736** review records pass.
Shared Spanish `r-is` and composed trigger grammar remain unresolved.
Attachment/member rule participles still need context-specific agreement.
Three Esperanto restored findings remain: lime color and two Complete labels.
Broader language and browser verification remain open; nothing pushed.
Latest unchanged review: **2026-09-14**, commit `ffde8e84e` — retained
17 Basque rule action and date-field labels. Shared `r-is` still contains
Spanish `es`; repair requires coordinated Basque trigger phrase composition.
Compound trigger phrases and relational top/bottom wording remain open.
Basque free wording and nine Galician restored findings remain context-dependent.
Galician export free wording remains open pending context.
Galician Complete and migration-complete wording remain open pending context.
Card-show-lists context is verified against its settings row and toggle.
Fixed categories include wrong-language prose, terminology, warnings,
@@ -30,7 +39,7 @@ Klingon findings and the broader 829 German-identical values were repaired;
the cron label retains the actual tool name Cron. Danish restored review
validated 611 values: 606 retained unchanged and five meanings repaired.
Four Danish context-dependent labels remain: free, Complete, Schedule and Days Old.
Esperanto overtime and lime-color wording
Esperanto lime-color wording
remains open.
Reviewed subtask actions/settings retain correct subordinate-task compounds.
@@ -73,5 +82,5 @@ Not all wrong values came from Transifex: the audit tracks 4,061 pulled
changes and 16,020 additional local findings; some Bosnian errors predate
the pull. Correct-language human translations remain preferred. Keep the
full review scope open until language and browser validation is supported
by evidence. Requested issue #6692 awaits actionable LDAP bind diagnostics;
translation work continues independently.
by evidence. Issue #6692 post-bind optional-filter TypeError is repaired in local commit
`2c5cdd78a`; live LDAP login remains unverified. Translation work continues.
@@ -10,7 +10,7 @@ This is without container (without Docker or Snap).
Right click and download files 1-4:
1. [wekan-11.77-win64.zip](https://github.com/wekan/wekan/releases/download/v11.77/wekan-11.77-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
1. [wekan-11.78-win64.zip](https://github.com/wekan/wekan/releases/download/v11.78/wekan-11.78-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
2. [node.exe](https://nodejs.org/dist/latest-v24.x/win-x64/node.exe)
@@ -22,7 +22,7 @@ Right click and download files 1-4:
6. Double click `mongodb-windows-x86_64-7.0.34-signed.msi` . In installer, uncheck downloading MongoDB compass.
7. Unzip `wekan-11.77-win64.zip` , inside it is directory `bundle`, to it copy other files:
7. Unzip `wekan-11.78-win64.zip` , inside it is directory `bundle`, to it copy other files:
```
bundle (directory)
+5 -5
View File
@@ -729,7 +729,7 @@
"filter-to-selection": "Filtri al elekto",
"other-filters-label": "Aliaj filtriloj",
"advanced-filter-label": "Altnivela filtrilo",
"advanced-filter-description": "La altnivela filtrilo permesas skribi ĉenon kun la jenaj operatoroj: == != <= >= && || ( ) Spaco estas uzata kiel apartigilo inter la operatoroj. Vi povas filtri laŭ ĉiuj propraj kampoj tajpante iliajn nomojn kaj valorojn. Ekzemple: Kampo1 == Valoro1. Rimarko: se kampoj aŭ valoroj enhavas spacojn, vi devas meti ilin inter unuopajn citilojn. Ekzemple: 'Kampo 1' == 'Valoro 1'. Por preterlasi unuopajn regsignojn (' \\/) vi povas uzi \\. Ekzemple: Kampo1 == I\\'m. Vi ankaŭ povas kombini plurajn kondiĉojn. Ekzemple: F1 == V1 || F1 == V2. Normale ĉiuj operatoroj estas interpretataj de maldekstre dekstren. Vi povas ŝanĝi la ordon per krampoj. Ekzemple: F1 == V1 && ( F2 == V2 || F2 == V3 ). Vi ankaŭ povas serĉi en tekstaj kampoj per regulesprimo: F1 == /Tes.*/i",
"advanced-filter-description": "La altnivela filtrilo permesas skribi ĉenon kun la jenaj operatoroj: == != <= >= && || ( ) Spaco estas uzata kiel apartigilo inter la operatoroj. Vi povas filtri laŭ ĉiuj propraj kampoj tajpante iliajn nomojn kaj valorojn. Ekzemple: Field1 == Value1. Rimarko: se kampoj aŭ valoroj enhavas spacojn, vi devas meti ilin inter unuopajn citilojn. Ekzemple: 'Field 1' == 'Value 1'. Por trakti la specialajn signojn (' \\/) kiel literalajn signojn, vi povas uzi \\. Ekzemple: Field1 == I\\'m. Vi ankaŭ povas kombini plurajn kondiĉojn. Ekzemple: F1 == V1 || F1 == V2. Normale ĉiuj operatoroj estas interpretataj de maldekstre dekstren. Vi povas ŝanĝi la ordon per krampoj. Ekzemple: F1 == V1 && ( F2 == V2 || F2 == V3 ). Vi ankaŭ povas serĉi en tekstaj kampoj per regulesprimo: F1 == /Tes.*/i",
"text-contains-trigger-label": "Teksto serĉenda",
"text-contains-trigger-description": "Kongruas kiam karto estas kreita, aŭ ĝia titolo aŭ priskribo estas ŝanĝita, kaj la nova teksto enhavas ĉi tion (sen distingo inter majuskloj kaj minuskloj).",
"fullname": "Plena nomo",
@@ -979,8 +979,8 @@
"this-board": "ĉi tiu tabulo",
"this-card": "ĉi tiu karto",
"spent-time-hours": "Pasigita tempo (horoj)",
"overtime-hours": "Kroma tempo (horoj)",
"overtime": "Kroma tempo",
"overtime-hours": "Kromlaboro (horoj)",
"overtime": "Kromlaboro",
"flowtime": "Flutempo",
"flow-start": "Ekigi Fluon",
"flow-stop": "Halti Fluon",
@@ -991,7 +991,7 @@
"pomodoro-work": "Laboro",
"pomodoro-break": "Paŭzo",
"pomodoro-completed": "%s kompletigitaj",
"has-overtime-cards": "Havas kartojn kun kroma tempo",
"has-overtime-cards": "Havas kartojn kun kromlaboro",
"has-spenttime-cards": "Havas kartojn kun pasigita tempo",
"time": "Tempo",
"title": "Titolo",
@@ -1966,7 +1966,7 @@
"Node_heap_heap_size_limit": "Node-amaso: limo de amasa grando",
"Node_heap_malloced_memory": "Node-amaso: memoro asignita per malloc",
"Node_heap_peak_malloced_memory": "Node-amaso: pinta memoro asignita per malloc",
"Node_heap_does_zap_garbage": "Node-amaso: ĉu nuligas rubon",
"Node_heap_does_zap_garbage": "Node-amaso: ĉu superskribas rubon per bitŝablono",
"Node_heap_number_of_native_contexts": "Node-amaso: nombro de indiĝenaj kuntekstoj",
"Node_heap_number_of_detached_contexts": "Node-amaso: nombro de malligitaj kuntekstoj",
"Node_memory_usage_rss": "Node-memoruzo: grando de la rezida aro (RSS)",
+7 -7
View File
@@ -41,10 +41,10 @@
"act-setCustomField": "__customField__ eremu pertsonalizatua editatu du: __customFieldValue__, __card__ txartelean, __list__ zerrendan, __swimlane__ errailean, __board__ arbelean",
"act-createList": "__list__ zerrenda gehitu du __board__ arbelera",
"act-addBoardMember": "__member__ kidea gehitu du __board__ arbelera",
"act-archivedBoard": "__board__ arbela biltegira eraman da",
"act-archivedCard": "__card__ txartela biltegira eraman da, __list__ zerrendatik, __swimlane__ errailean, __board__ arbelean",
"act-archivedList": "__list__ zerrenda biltegira eraman da, __swimlane__ errailean, __board__ arbelean",
"act-archivedSwimlane": "__swimlane__ erraila biltegira eraman da, __board__ arbelean",
"act-archivedBoard": "__board__ arbela artxibora eraman da",
"act-archivedCard": "__card__ txartela artxibora eraman da, __list__ zerrendatik, __swimlane__ errailean, __board__ arbelean",
"act-archivedList": "__list__ zerrenda artxibora eraman da, __swimlane__ errailean, __board__ arbelean",
"act-archivedSwimlane": "__swimlane__ erraila artxibora eraman da, __board__ arbelean",
"act-importBoard": "__board__ arbela inportatu da",
"act-importCard": "__board__ arbeleko __swimlane__  erraileko __list__  zerrendako __card__ txartela inportatu da",
"act-importList": "__board__ arbeleko __swimlane__ erraileko __list__ zerrenda inportatu da",
@@ -1339,8 +1339,8 @@
"set-filter": "Ezarri iragazkia",
"r-moved-to": "Eramaten denean hona:",
"r-moved-from": "Eramaten denean hemendik:",
"r-archived": "Biltegira eraman da",
"r-unarchived": "Biltegitik berreskuratu da",
"r-archived": "Artxibora eraman da",
"r-unarchived": "Artxibotik berreskuratu da",
"r-a-card": "txartel bat",
"r-when-a-label-is": "Etiketa bat",
"r-when-the-label": "Etiketa hau",
@@ -1773,7 +1773,7 @@
"globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - fitxes que es van modificar *<n>* fa dies o menys",
"globalSearch-instructions-operator-status": "`__operator_status__:<status>` - non *<status>* hauetako bat den:",
"globalSearch-instructions-status-archived": "`__predicate_archived__` - fitxes arxivades",
"globalSearch-instructions-status-all": "`__predicate_all__` - biltegiratutako eta biltegiratu gabeko txartel guztiak",
"globalSearch-instructions-status-all": "`__predicate_all__` - artxibatutako eta artxibatu gabeko txartel guztiak",
"globalSearch-instructions-status-ended": "`__predicate_ended__`: fitxes amb una data de finalització",
"globalSearch-instructions-status-public": "`__predicate_public__` - fitxes només als taulers públics",
"globalSearch-instructions-status-private": "`__predicate_private__` - arbel pribatuetako txartelak soilik",
+10 -10
View File
@@ -752,7 +752,7 @@
"import-board-instruction-asana": "Pega un JSON de exportación de tarefas de Asana ({ \"data\": [...] } da exportación de Asana ou GET /tasks). As tarefas agrúpanse en seccións (memberships); as tarefas completadas van a Feito.",
"import-board-instruction-zenkit": "Pega un JSON de exportación de lista de ZenKit ({ \"title\", \"stages\":[...], \"items\":[...] }). As stages convértense en listas e os items en cartões.",
"import-board-instruction-markdown": "Pegue unha lista de tarefas en markdown (a convención que usan ferramentas markdown-kanban como Obsidian Kanban): cabeceiras \"## Nome da lista\", con elementos \"- [ ]\"/\"- [x]\" debaixo. As cabeceiras convértense en listas e os elementos en tarxetas; unha simple lista de viñetas sen caixas tamén se importa, como tarxetas abertas.",
"import-board-instruction-trello": "No teu taboleiro de Trello, vai a 'Menú', despois a 'Máis', 'Imprimir e exportar', 'Exportar JSON', e copia o texto resultante.",
"import-board-instruction-trello": "No teu taboleiro de Trello, vai a 'Menu', despois a 'More', 'Print and Export', 'Export JSON', e copia o texto resultante.",
"import-board-instruction-csv": "Pega os teus valores separados por comas (CSV) / valores separados por tabulacións (TSV).",
"import-board-instruction-jira": "Pega o JSON da busca de incidencias da API REST de Jira Cloud (GET /rest/api/2/search), ou un obxecto equivalente { \"issues\": [...] }. Os estados de Jira convértense en listas e as incidencias en cartões. Opcionalmente inclúe unha matriz \"automationRules\" para importar regras.",
"import-board-instruction-excel": "Escolle unha folla de cálculo .xlsx ao estilo de WeKan (as mesmas columnas que a exportación a Excel: Title, Description, Status/List, Members, Labels, datas, campos personalizados). A primeira fila é a cabeceira.",
@@ -1167,8 +1167,8 @@
"team-propagate-members-to-boards": "Propagar membros aos quadros",
"team-sync-members-from-auth": "Sincronizar membros desde o provedor de autenticación",
"active-person": "Persoa activa",
"card-received": "Recibido",
"card-received-on": "Recibido o",
"card-received": "Recibida",
"card-received-on": "Recibida o",
"card-end": "Fim",
"card-end-on": "Remata o",
"editCardReceivedDatePopup-title": "Cambiar a data de recepción",
@@ -1228,7 +1228,7 @@
"activity-removed-label-card": "quitou a etiqueta '%s'",
"activity-delete-attach-card": "eliminou un anexo",
"activity-set-customfield": "estableceu o campo personalizado '%s' a '%s' en %s",
"activity-unset-customfield": "quitou o campo personalizado '%s' en %s",
"activity-unset-customfield": "quitou o valor do campo personalizado '%s' en %s",
"r-rule": "Regra",
"r-add-trigger": "Engadir disparador",
"r-add-action": "Engadir acción",
@@ -1353,10 +1353,10 @@
"r-when-a-attach": "Cando un anexo",
"r-when-a-card-matches-advanced-filter": "Cando unha tarxeta coincide co filtro avanzado",
"r-when-a-card-title-or-description-contains": "Cando o título ou a descrición da tarxeta contén",
"r-when-a-due-date-changed": "Data limite: a data cambiou",
"r-when-a-start-date-changed": "Data de início: a data cambiou",
"r-when-a-end-date-changed": "Fim: a data cambiou",
"r-when-a-received-date-changed": "Recebido: a data cambiou",
"r-when-a-due-date-changed": "Cando se establece ou cambia a data de vencemento",
"r-when-a-start-date-changed": "Cando se establece ou cambia a data de inicio",
"r-when-a-end-date-changed": "Cando se establece ou cambia a data de fin",
"r-when-a-received-date-changed": "Cando se establece ou cambia a data de recepción",
"r-when-a-checklist": "Cando unha lista de verificación é",
"r-when-the-checklist": "Cando a lista de verificación",
"r-completed": "Completada",
@@ -1966,7 +1966,7 @@
"Node_heap_heap_size_limit": "Memoria heap de Node: límite de tamaño do heap",
"Node_heap_malloced_memory": "Memoria heap de Node: memoria asignada con malloc",
"Node_heap_peak_malloced_memory": "Memoria heap de Node: pico de memoria asignada con malloc",
"Node_heap_does_zap_garbage": "Memoria heap de Node: limpa o lixo (zap garbage)",
"Node_heap_does_zap_garbage": "Memoria heap de Node: sobrescribe o lixo cun patrón de bits",
"Node_heap_number_of_native_contexts": "Memoria heap de Node: número de contextos nativos",
"Node_heap_number_of_detached_contexts": "Memoria heap de Node: número de contextos desvinculados",
"Node_memory_usage_rss": "Uso de memoria de Node: tamaño do conxunto residente",
@@ -2480,7 +2480,7 @@
"step-fix-file-urls": "Corrixindo os URL dos ficheiros",
"cleanup": "Limpeza",
"cleanup-old-jobs": "Limpar os traballos antigos",
"completed": "Completado",
"completed": "Completada",
"conversion-info-text": "Esta conversión realízase unha vez por taboleiro e mellora o rendemento. Pode seguir usando o taboleiro con normalidade.",
"converting-board": "Convertendo o taboleiro",
"converting-board-description": "Convertendo a estrutura do taboleiro para mellorar a funcionalidade. Pode tardar uns instantes.",
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "wekan",
"version": "v11.77.0",
"version": "v11.78.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wekan",
"version": "v11.77.0",
"version": "v11.78.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v11.77.0",
"version": "v11.78.0",
"description": "Open-Source kanban",
"private": true,
"repository": {
+7 -1
View File
@@ -369,7 +369,13 @@ export default class LDAP {
getUserFilter(username) {
const filter = [];
if (this.options.User_Search_Filter !== '') {
// The optional filter may be unset, especially in snap configurations.
// Binding succeeds before this method runs; indexing undefined made the
// subsequent search look like a bind failure (#6692).
if (this.options.User_Search_Filter != null && this.options.User_Search_Filter !== '') {
if (typeof this.options.User_Search_Filter !== 'string') {
throw new Error('LDAP_USER_SEARCH_FILTER must be a string');
}
if (this.options.User_Search_Filter[0] === '(') {
filter.push(`${this.options.User_Search_Filter}`);
} else {
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf8" />
<title>WeKan REST API v11.77</title>
<title>WeKan REST API v11.78</title>
<!-- needed for adaptive design -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@@ -427,7 +427,7 @@ data-styled.g139[id="sc-iIvZUO"]{content:"kAmnlO,"}/*!sc*/
55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864
-231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688,
-104.0616 -231.873,-231.248 z
" fill="currentColor"></path></g></svg></div></div><div class="sc-jtyQFi ldXCvc api-content"><div class="sc-eCQgVK jleRXN"><div class="sc-iCECmn lhRBUS"><div class="sc-hKVpXn kktglN api-info"><h1 class="sc-fuztkK sc-ctiVdb eRMVDe cBgvIi">Wekan REST API<!-- --> <span>(<!-- -->v11.77<!-- -->)</span></h1><p>Download OpenAPI specification<!-- -->:</p><div class="sc-iJSMbW sc-cBEgGa fiNpIH htbHfQ"></div><div data-role="redoc-summary" html="" class="sc-iJSMbW sc-cBEgGa fiNpIH htbHfQ"></div><div data-role="redoc-description" html="&lt;p&gt;The REST API allows you to control and extend Wekan with ease.&lt;/p&gt;
" fill="currentColor"></path></g></svg></div></div><div class="sc-jtyQFi ldXCvc api-content"><div class="sc-eCQgVK jleRXN"><div class="sc-iCECmn lhRBUS"><div class="sc-hKVpXn kktglN api-info"><h1 class="sc-fuztkK sc-ctiVdb eRMVDe cBgvIi">Wekan REST API<!-- --> <span>(<!-- -->v11.78<!-- -->)</span></h1><p>Download OpenAPI specification<!-- -->:</p><div class="sc-iJSMbW sc-cBEgGa fiNpIH htbHfQ"></div><div data-role="redoc-summary" html="" class="sc-iJSMbW sc-cBEgGa fiNpIH htbHfQ"></div><div data-role="redoc-description" html="&lt;p&gt;The REST API allows you to control and extend Wekan with ease.&lt;/p&gt;
&lt;p&gt;If you are an end-user and not a dev or a tester, &lt;a href=&quot;https://github.com/wekan/wekan/issues/new&quot;&gt;create an issue&lt;/a&gt; to request new APIs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All API calls in the documentation are made using &lt;code&gt;curl&lt;/code&gt;. However, you are free to use Java / Python / PHP / Golang / Ruby / Swift / Objective-C / Rust / Scala / C# or any other programming languages.&lt;/p&gt;
@@ -2543,7 +2543,7 @@ trigger and action stay as they are); true resumes it</p>
" class="sc-iJSMbW sc-cBEgGa sc-ciCrSJ fiNpIH iHFyeq dDDioG"><p>200 response</p>
</div></button></div></div></div><div class="sc-jSppWd sc-gKkgUA fpMlmc bdQQyo"><div class="sc-fXwuWv fYxpnv"><button class="sc-jWMFtl jzaJhV"><span type="post" class="sc-eEFuoE hOENFc http-verb post">post</span><span class="sc-FpjRO dsIRaZ">/api/boards/{board}/swimlanes/{swimlane}/move</span><svg class="sc-dYjPD bQKUih" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-fmtEmb tUxhh"><div class="sc-ljIcGq bAnPPh"><div html="" class="sc-iJSMbW sc-cBEgGa fiNpIH bWgCZK"></div><div tabindex="0" role="button"><div class="sc-jlJOIR PEJyb"><span></span>/api/boards/{board}/swimlanes/{swimlane}/move</div></div></div></div></div></div></div></div></div><div class="sc-ekVkVN bjzTxL"></div></div></div>
<script>
const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.0","info":{"title":"Wekan REST API","version":"v11.77","description":"The REST API allows you to control and extend Wekan with ease.\n\nIf you are an end-user and not a dev or a tester, [create an issue](https://github.com/wekan/wekan/issues/new) to request new APIs.\n\n> All API calls in the documentation are made using `curl`. However, you are free to use Java / Python / PHP / Golang / Ruby / Swift / Objective-C / Rust / Scala / C# or any other programming languages.\n\n# Production Security Concerns\nWhen calling a production Wekan server, ensure it is running via HTTPS and has a valid SSL Certificate. The login method requires you to post your username and password in plaintext, which is why we highly suggest only calling the REST login api over HTTPS. Also, few things to note:\n\n* Only call via HTTPS\n* Implement a timed authorization token expiration strategy\n* Ensure the calling user only has permissions for what they are calling and no more\n"},"paths":{"/users/login":{"post":{"operationId":"login","summary":"Login with REST API","tags":["Login"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"}}}}},"description":"Login credentials","required":true},"responses":{"200":{"description":"Successful authentication","content":{"*/*":{"schema":{"type":"object","required":["id","token","tokenExpires"],"properties":{"id":{"type":"string","description":"User ID"},"token":{"type":"string","description":"Authentication token\n"},"tokenExpires":{"type":"string","format":"date-time","description":"Token expiration date\n"}}}}}},"400":{"description":"Error in authentication\n","content":{"*/*":{"schema":{"type":"object","properties":{"error":{"type":"string"},"reason":{"type":"string"}}}}}},"default":{"description":"Error in authentication\n"}}}},"/users/register":{"post":{"operationId":"register","summary":"Register with REST API","description":"Notes:\n - You will need to provide the token for any of the authenticated methods.\n","tags":["Login"],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"},"email":{"description":"Your email\n","type":"string"}},"required":["username","password","email"]}}},"required":true},"responses":{"200":{"description":"Successful registration","content":{"*/*":{"schema":{"items":{"properties":{"id":{"type":"string"},"token":{"type":"string"},"tokenExpires":{"type":"string"}}}}}}},"400":{"description":"Error in registration\n","content":{"*/*":{"schema":{"items":{"properties":{"error":{"type":"number"},"reason":{"type":"string"}}}}}}},"default":{"description":"Error in registration\n"}}}},"/api/admin/attachment-settings":{"get":{"operationId":"get_attachment_settings","summary":"Get the attachment storage / upload-block settings (GlobalAdmin)","description":"Only the global admin can call this. Returns the Admin Panel >\n Attachments settings document, including `limitSettings.attachmentsUploadBlocked`\n and `limitSettings.avatarsUploadBlocked` plus the size limits. Cloud-storage\n secret keys (S3 secretAccessKey, Azure accountKey/connectionString, GCS\n credentials) are masked and never returned; a boolean `<field>Set` marker\n shows whether a value exists.\n","tags":["AttachmentStorageSettings","Settings"],"security":[{"UserSecurity":[]}],"responses":{"200":{"description":"200 response","content":{"application/json":{"schema":{"type":"Object"}}}}}},"put":{"operationId":"update_attachment_settings","summary":"Update the attachment storage / upload-block settings (GlobalAdmin)","description":"Only the global admin can call this. The request body is a\n partial settings object (for example\n `{\"limitSettings\": {\"avatarsUploadBlocked\": true}}`). It is applied through\n the same update path as the `updateAttachmentStorageSettings` Meteor method,\n so validation and `$set` semantics are identical: cloud-storage secrets left\n blank are preserved. The masked, updated settings document is returned.\n","tags":["AttachmentStorageSettings","Settings"],"security":[{"UserSecurity":[]}],"responses":{"200":{"description":"200 response","content":{"application/json":{"schema":{"type":"Object"}}}}}}},"/api/admin/domains":{"get":{"operationId":"get_admin_domains","summary":"List email domains with their user counts (GlobalAdmin)","description":"Only the Line truncated
const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.0","info":{"title":"Wekan REST API","version":"v11.78","description":"The REST API allows you to control and extend Wekan with ease.\n\nIf you are an end-user and not a dev or a tester, [create an issue](https://github.com/wekan/wekan/issues/new) to request new APIs.\n\n> All API calls in the documentation are made using `curl`. However, you are free to use Java / Python / PHP / Golang / Ruby / Swift / Objective-C / Rust / Scala / C# or any other programming languages.\n\n# Production Security Concerns\nWhen calling a production Wekan server, ensure it is running via HTTPS and has a valid SSL Certificate. The login method requires you to post your username and password in plaintext, which is why we highly suggest only calling the REST login api over HTTPS. Also, few things to note:\n\n* Only call via HTTPS\n* Implement a timed authorization token expiration strategy\n* Ensure the calling user only has permissions for what they are calling and no more\n"},"paths":{"/users/login":{"post":{"operationId":"login","summary":"Login with REST API","tags":["Login"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"}}}}},"description":"Login credentials","required":true},"responses":{"200":{"description":"Successful authentication","content":{"*/*":{"schema":{"type":"object","required":["id","token","tokenExpires"],"properties":{"id":{"type":"string","description":"User ID"},"token":{"type":"string","description":"Authentication token\n"},"tokenExpires":{"type":"string","format":"date-time","description":"Token expiration date\n"}}}}}},"400":{"description":"Error in authentication\n","content":{"*/*":{"schema":{"type":"object","properties":{"error":{"type":"string"},"reason":{"type":"string"}}}}}},"default":{"description":"Error in authentication\n"}}}},"/users/register":{"post":{"operationId":"register","summary":"Register with REST API","description":"Notes:\n - You will need to provide the token for any of the authenticated methods.\n","tags":["Login"],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"},"email":{"description":"Your email\n","type":"string"}},"required":["username","password","email"]}}},"required":true},"responses":{"200":{"description":"Successful registration","content":{"*/*":{"schema":{"items":{"properties":{"id":{"type":"string"},"token":{"type":"string"},"tokenExpires":{"type":"string"}}}}}}},"400":{"description":"Error in registration\n","content":{"*/*":{"schema":{"items":{"properties":{"error":{"type":"number"},"reason":{"type":"string"}}}}}}},"default":{"description":"Error in registration\n"}}}},"/api/admin/attachment-settings":{"get":{"operationId":"get_attachment_settings","summary":"Get the attachment storage / upload-block settings (GlobalAdmin)","description":"Only the global admin can call this. Returns the Admin Panel >\n Attachments settings document, including `limitSettings.attachmentsUploadBlocked`\n and `limitSettings.avatarsUploadBlocked` plus the size limits. Cloud-storage\n secret keys (S3 secretAccessKey, Azure accountKey/connectionString, GCS\n credentials) are masked and never returned; a boolean `<field>Set` marker\n shows whether a value exists.\n","tags":["AttachmentStorageSettings","Settings"],"security":[{"UserSecurity":[]}],"responses":{"200":{"description":"200 response","content":{"application/json":{"schema":{"type":"Object"}}}}}},"put":{"operationId":"update_attachment_settings","summary":"Update the attachment storage / upload-block settings (GlobalAdmin)","description":"Only the global admin can call this. The request body is a\n partial settings object (for example\n `{\"limitSettings\": {\"avatarsUploadBlocked\": true}}`). It is applied through\n the same update path as the `updateAttachmentStorageSettings` Meteor method,\n so validation and `$set` semantics are identical: cloud-storage secrets left\n blank are preserved. The masked, updated settings document is returned.\n","tags":["AttachmentStorageSettings","Settings"],"security":[{"UserSecurity":[]}],"responses":{"200":{"description":"200 response","content":{"application/json":{"schema":{"type":"Object"}}}}}}},"/api/admin/domains":{"get":{"operationId":"get_admin_domains","summary":"List email domains with their user counts (GlobalAdmin)","description":"Only the Line truncated
var container = document.getElementById('redoc');
Redoc.hydrate(__redoc_state, container);
+1 -1
View File
@@ -1,7 +1,7 @@
swagger: '2.0'
info:
title: Wekan REST API
version: v11.77
version: v11.78
description: |
The REST API allows you to control and extend Wekan with ease.
+39
View File
@@ -501,3 +501,42 @@ If a comment references a parent missing from the paginated issue inventory, the
collector fetches that issue and any pull metadata through the same rate-limited
API client, saves them directly to their issue/pull directory and continues. A
failed parent fetch preserves the checkpoint so a subsequent run retries the page.
### Staged synchronization and concurrent destinations
Menu option 1 first synchronizes the selected source Git branches and tags
with every active destination. Only after Git succeeds does it collect source
issues, pull conversations, releases and linked files into the durable
`.tools/mirror/<host>/<organization>/<repo>/` archive and generate static pages.
An archive failure stops destination content writes.
After the local archive is ready, separate destination processes synchronize
its content concurrently. They reuse the same snapshot, preserve completed
resume checkpoints, and retain each forge's rate-limit handling. Live progress
shows every active mirror together: start time, processed issues/total,
processed releases/total and running/finished/failed state. Processed counters
include checked existing items, failed items and unsupported/draft releases;
they measure work examined, while copied/failed logs describe the result.
Each run retains the combined mirror-log.txt plus github.txt, gitlab.txt,
codeberg.txt or sourceforge.txt beside it, according to active destinations,
in `.tools/log/mirror/YYYY-MM-DD_HH-MM-SS/`. These separate logs include Git
and destination content output. Starting this script performs remote writes
and is a human maintainer operation; offline tests use injected local commands.
### Unavailable linked files and historical recovery
Unavailable public issue/comment links are checked against archive.org's
Wayback availability API using the content creation timestamp. The closest
available successful capture is downloaded into the same comment directory,
with recoveredFrom and captureTimestamp provenance in mirror-index.json.
Original URLs remain the synchronization identity. Current links bypass this
fallback; archive.org failures do not cause recursive recovery attempts.
Attachment requests have a 30-second deadline covering redirects and body
transfer. Long saved cooldowns and new rate limits defer the attachment to a
later run instead of waiting; the saved cooldown remains respected. Optional
linked-file failures are logged as skipped and preserve existing local bytes,
so remaining content and target synchronization can continue. Repository/API
inventory failures still stop incomplete synchronization. Private/local links
remain blocked and no forge credentials are sent to archive.org or linked sites.
+2 -1
View File
@@ -1,7 +1,8 @@
#!/bin/bash
#
# provenance-table.sh - turn the collected provenance rows into the markdown
# table that goes at the TOP of the release notes.
# diagnostic table for manual inspection; never append it to release notes
# or CHANGELOG.md. Build provenance stays in artifacts.
#
# Reads every provenance.tsv the build jobs uploaded (given as arguments, or
# found under ./provenance/) and prints a markdown section. Sorted by bundle,
+7
View File
@@ -40,6 +40,13 @@ if [ -n "${ZSH_VERSION:-}" ]; then exec /bin/bash "$0" "$@"; fi
# - builds: tags v<new>, builds amd64/arm64/win64/mac bundles, creates the
# GitHub Release, builds+pushes Docker images, builds the snap.
#
# Release notes summarize translation details as updated language names.
# Add **Languages updated:** metadata beneath the Translations group label.
# Notes include only In short, Security, translation languages, thanks and
# the changelog link. Other details stay in CHANGELOG.md. Never append
# "Binaries in these bundles" or a provenance table; retain provenance.tsv
# build artifacts and checksum verification separately.
#
# Track progress at: https://github.com/wekan/wekan/actions
#
# Required GitHub secrets (DOCKERHUB_AUTH, QUAY_AUTH, GHCR_AUTH, SNAP_AUTH,
+51 -5
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Print the CHANGELOG.md section that is this release's notes, to stdout.
# Print release notes, summarizing translation details as affected languages.
#
# Usage: releases/release-notes.sh <version> [changelog-file]
#
@@ -55,13 +55,59 @@ with open(os.environ["CHANGELOG_FILE"], encoding="utf-8") as f:
content = f.read()
patterns = [
rf"(# v{re.escape(version)} .*?)(?=\n# v[0-9]|\Z)",
r"(# Upcom\w* WeKan.*?)(?=\n# v[0-9]|\Z)",
rf"(^# v{re.escape(version)} [^\n]*\n.*?)(?=^# v[0-9]|\Z)",
r"(^# Upcom\w* WeKan [^\n]*\n.*?)(?=^# v[0-9]|\Z)",
]
for pat in patterns:
m = re.search(pat, content, re.DOTALL)
m = re.search(pat, content, re.DOTALL | re.MULTILINE)
if m:
print(m.group(1).strip())
notes = m.group(1).strip()
summary = re.search(r"^\*\*In short:\*\* (.*?)(?=\n\s*\n|\Z)", notes, re.MULTILINE | re.DOTALL)
if not summary:
sys.stderr.write("::error::release-notes: selected release needs an **In short:** summary.\n")
sys.exit(1)
lines = notes.splitlines(keepends=True)
security, languages = [], set()
i = 0
while i < len(lines):
line = lines[i]
is_translation = bool(re.match(r"^\*\*Translations\*\*", line))
is_security = bool(re.match(r"^\*\*Security(?: hardening)?\*\* - ", line, re.IGNORECASE))
is_security_header = bool(re.match(r"^(?:This release |and ).*(?:CRITICAL SECURITY ISSUE|security hardening)", line, re.IGNORECASE))
if not (is_translation or is_security or is_security_header):
i += 1
continue
j, depth = i + 1, 0
while j < len(lines):
next_line = lines[j]
boundary = re.match(r"^(?:\*\*[^\n]+\*\* - |and |This release |Thanks to above GitHub)", next_line)
if depth == 0 and boundary:
break
depth += len(re.findall(r"<details(?:\s[^>]*)?>", next_line))
depth -= next_line.count("</details>")
j += 1
block = "".join(lines[i + 1:j]).strip()
if is_translation:
names = re.search(r"^\*\*Languages updated:\*\* (.+)$", block, re.MULTILINE)
if not names:
sys.stderr.write("::error::release-notes: Translations group needs **Languages updated:** followed by comma-separated language names.\n")
sys.exit(1)
languages.update(name.strip() for name in names.group(1).split(",") if name.strip())
elif block:
security.append((line.strip() + "\n\n" if is_security else "") + block)
i = j
heading = notes.splitlines()[0].removeprefix("# ")
if heading.startswith("Upcoming"):
from datetime import date
heading = f"v{version} {date.today().isoformat()} WeKan ® release"
anchor = re.sub(r"[^\w\s-]", "", heading.lower())
anchor = re.sub(r"\s", "-", anchor)
output = ["## In short\n\n" + summary.group(1).strip(),
"## Security\n\n" + ("\n\n".join(security) if security else "No security changes."),
"## Translations\n\n" + ("\n".join("- " + name for name in sorted(languages, key=str.casefold)) if languages else "No translation updates."),
"Thanks to above GitHub users for their contributions and translators for their translations.",
f"[More details at ChangeLog](https://github.com/wekan/wekan/blob/main/CHANGELOG.md#{anchor})"]
print("\n\n".join(output))
sys.exit(0)
# Neither section exists. Print nothing; the caller turns that into an error
# with a message that says what to add, rather than publishing an empty release.
@@ -127055,5 +127055,159 @@
"before": "Malsukcesa fenestro (sekundoj)",
"after": "Tempofenestro por malsukcesoj (sekundoj)",
"reason": "2026-09-14: Failure window is a time interval for counting failed attempts, not a failed window. Make the temporal and failure-counting relationship explicit; preserve seconds unit."
},
{
"locale": "eo",
"key": "Node_heap_does_zap_garbage",
"before": "Node-amaso: ĉu nuligas rubon",
"after": "Node-amaso: ĉu superskribas rubon per bitŝablono",
"reason": "2026-09-14: Official Node V8 docs specify heap garbage overwritten with a bit pattern; nuligas incorrectly suggests zeroing. Translate overwrite and bit-pattern explicitly. https://nodejs.org/api/v8.html#v8getheapstatistics"
},
{
"locale": "eo",
"key": "advanced-filter-description",
"before": "La altnivela filtrilo permesas skribi ĉenon kun la jenaj operatoroj: == != <= >= && || ( ) Spaco estas uzata kiel apartigilo inter la operatoroj. Vi povas filtri laŭ ĉiuj propraj kampoj tajpante iliajn nomojn kaj valorojn. Ekzemple: Kampo1 == Valoro1. Rimarko: se kampoj aŭ valoroj enhavas spacojn, vi devas meti ilin inter unuopajn citilojn. Ekzemple: 'Kampo 1' == 'Valoro 1'. Por preterlasi unuopajn regsignojn (' \\/) vi povas uzi \\. Ekzemple: Kampo1 == I\\'m. Vi ankaŭ povas kombini plurajn kondiĉojn. Ekzemple: F1 == V1 || F1 == V2. Normale ĉiuj operatoroj estas interpretataj de maldekstre dekstren. Vi povas ŝanĝi la ordon per krampoj. Ekzemple: F1 == V1 && ( F2 == V2 || F2 == V3 ). Vi ankaŭ povas serĉi en tekstaj kampoj per regulesprimo: F1 == /Tes.*/i",
"after": "La altnivela filtrilo permesas skribi ĉenon kun la jenaj operatoroj: == != <= >= && || ( ) Spaco estas uzata kiel apartigilo inter la operatoroj. Vi povas filtri laŭ ĉiuj propraj kampoj tajpante iliajn nomojn kaj valorojn. Ekzemple: Field1 == Value1. Rimarko: se kampoj aŭ valoroj enhavas spacojn, vi devas meti ilin inter unuopajn citilojn. Ekzemple: 'Field 1' == 'Value 1'. Por trakti la specialajn signojn (' \\/) kiel literalajn signojn, vi povas uzi \\. Ekzemple: Field1 == I\\'m. Vi ankaŭ povas kombini plurajn kondiĉojn. Ekzemple: F1 == V1 || F1 == V2. Normale ĉiuj operatoroj estas interpretataj de maldekstre dekstren. Vi povas ŝanĝi la ordon per krampoj. Ekzemple: F1 == V1 && ( F2 == V2 || F2 == V3 ). Vi ankaŭ povas serĉi en tekstaj kampoj per regulesprimo: F1 == /Tes.*/i",
"reason": "2026-09-14: Preserve shared literal executable Field1/Value1 and quoted field examples; describe escaping special punctuation as literal characters, not skipping control characters. Operators, backslash inventory and regex example remain unchanged."
},
{
"locale": "eo",
"key": "overtime",
"before": "Kroma tempo",
"after": "Kromlaboro",
"reason": "2026-09-14: The manually selected isOvertime flag classifies recorded work hours, not a deadline or granted extra time. Use Esperanto kromlaboro (overtime work) consistently instead of generic kroma tempo; hours unit retained. Dictionary references recorded in audit evidence."
},
{
"locale": "eo",
"key": "overtime-hours",
"before": "Kroma tempo (horoj)",
"after": "Kromlaboro (horoj)",
"reason": "2026-09-14: The manually selected isOvertime flag classifies recorded work hours, not a deadline or granted extra time. Use Esperanto kromlaboro (overtime work) consistently instead of generic kroma tempo; hours unit retained. Dictionary references recorded in audit evidence."
},
{
"locale": "eo",
"key": "has-overtime-cards",
"before": "Havas kartojn kun kroma tempo",
"after": "Havas kartojn kun kromlaboro",
"reason": "2026-09-14: The manually selected isOvertime flag classifies recorded work hours, not a deadline or granted extra time. Use Esperanto kromlaboro (overtime work) consistently instead of generic kroma tempo; hours unit retained. Dictionary references recorded in audit evidence."
},
{
"locale": "gl",
"key": "import-board-instruction-trello",
"before": "No teu taboleiro de Trello, vai a 'Menú', despois a 'Máis', 'Imprimir e exportar', 'Exportar JSON', e copia o texto resultante.",
"after": "No teu taboleiro de Trello, vai a 'Menu', despois a 'More', 'Print and Export', 'Export JSON', e copia o texto resultante.",
"reason": "2026-09-14: Restore literal source Trello navigation labels Menu/More/Print and Export/Export JSON, matching the existing audited import-instruction policy. Retain Galician instruction prose."
},
{
"locale": "gl",
"key": "r-when-a-due-date-changed",
"before": "Data limite: a data cambiou",
"after": "Cando se establece ou cambia a data de vencemento",
"reason": "Replace Portuguese wording and restore the English distinction between setting and changing the date in the Galician automation trigger."
},
{
"locale": "gl",
"key": "r-when-a-start-date-changed",
"before": "Data de início: a data cambiou",
"after": "Cando se establece ou cambia a data de inicio",
"reason": "Replace Portuguese wording and restore the English distinction between setting and changing the date in the Galician automation trigger."
},
{
"locale": "gl",
"key": "r-when-a-end-date-changed",
"before": "Fim: a data cambiou",
"after": "Cando se establece ou cambia a data de fin",
"reason": "Replace Portuguese wording and restore the English distinction between setting and changing the date in the Galician automation trigger."
},
{
"locale": "gl",
"key": "r-when-a-received-date-changed",
"before": "Recebido: a data cambiou",
"after": "Cando se establece ou cambia a data de recepción",
"reason": "Replace Portuguese wording and restore the English distinction between setting and changing the date in the Galician automation trigger."
},
{
"locale": "gl",
"key": "activity-unset-customfield",
"before": "quitou o campo personalizado '%s' en %s",
"after": "quitou o valor do campo personalizado '%s' en %s",
"reason": "The models/cards.js unsetCustomField activity clears an individual card custom-field value, not the custom-field definition. Preserve both percent tokens and state value removal explicitly."
},
{
"locale": "gl",
"key": "Node_heap_does_zap_garbage",
"before": "Memoria heap de Node: limpa o lixo (zap garbage)",
"after": "Memoria heap de Node: sobrescribe o lixo cun patrón de bits",
"reason": "Node V8 getHeapStatistics does_zap_garbage reports whether heap garbage is overwritten with a bit pattern, rather than garbage collection or cleaning. Node official V8 API documentation establishes the distinction."
},
{
"locale": "gl",
"key": "card-received",
"before": "Recibido",
"after": "Recibida",
"reason": "Received date headings and tooltips describe the card (Galician tarxeta, feminine), not a masculine received object. cardDetails.jade and cardDate.js establish the card/date context; retain the date-introducing o."
},
{
"locale": "gl",
"key": "card-received-on",
"before": "Recibido o",
"after": "Recibida o",
"reason": "Received date headings and tooltips describe the card (Galician tarxeta, feminine), not a masculine received object. cardDetails.jade and cardDate.js establish the card/date context; retain the date-introducing o."
},
{
"locale": "gl",
"key": "completed",
"before": "Completado",
"after": "Completada",
"reason": "client/components/cards/subtasks.jade uses completed as the completed-subtask icon tooltip. Galician subtarefa is feminine, requiring Completada rather than masculine Completado."
},
{
"locale": "eu",
"key": "act-archivedBoard",
"before": "__board__ arbela biltegira eraman da",
"after": "__board__ arbela artxibora eraman da",
"reason": "Use artxibo (archive), documented in UPV/EHU Egungo Euskararen Hiztegia https://www.ehu.eus/eeh/cgi/bila?h=artxibo, to distinguish archived board/card/list/swimlane state from a generic storage repository. Preserve every activity token."
},
{
"locale": "eu",
"key": "act-archivedCard",
"before": "__card__ txartela biltegira eraman da, __list__ zerrendatik, __swimlane__ errailean, __board__ arbelean",
"after": "__card__ txartela artxibora eraman da, __list__ zerrendatik, __swimlane__ errailean, __board__ arbelean",
"reason": "Use artxibo (archive), documented in UPV/EHU Egungo Euskararen Hiztegia https://www.ehu.eus/eeh/cgi/bila?h=artxibo, to distinguish archived board/card/list/swimlane state from a generic storage repository. Preserve every activity token."
},
{
"locale": "eu",
"key": "act-archivedList",
"before": "__list__ zerrenda biltegira eraman da, __swimlane__ errailean, __board__ arbelean",
"after": "__list__ zerrenda artxibora eraman da, __swimlane__ errailean, __board__ arbelean",
"reason": "Use artxibo (archive), documented in UPV/EHU Egungo Euskararen Hiztegia https://www.ehu.eus/eeh/cgi/bila?h=artxibo, to distinguish archived board/card/list/swimlane state from a generic storage repository. Preserve every activity token."
},
{
"locale": "eu",
"key": "act-archivedSwimlane",
"before": "__swimlane__ erraila biltegira eraman da, __board__ arbelean",
"after": "__swimlane__ erraila artxibora eraman da, __board__ arbelean",
"reason": "Use artxibo (archive), documented in UPV/EHU Egungo Euskararen Hiztegia https://www.ehu.eus/eeh/cgi/bila?h=artxibo, to distinguish archived board/card/list/swimlane state from a generic storage repository. Preserve every activity token."
},
{
"locale": "eu",
"key": "globalSearch-instructions-status-all",
"before": "`__predicate_all__` - biltegiratutako eta biltegiratu gabeko txartel guztiak",
"after": "`__predicate_all__` - artxibatutako eta artxibatu gabeko txartel guztiak",
"reason": "Search status-all includes archived and unarchived cards, not stored and unstored cards. Use archive terminology consistently with dictionary-verified artxibo activity captions and retain __predicate_all__ exactly."
},
{
"locale": "eu",
"key": "r-archived",
"before": "Biltegira eraman da",
"after": "Artxibora eraman da",
"reason": "Replace generic storage biltegi with archive artxibo, consistent with reviewed Basque activity/search archive terminology and EHU archive dictionary https://www.ehu.eus/eeh/cgi/bila?h=artxibo. Retain archive versus restore direction. Shared r-is Spanish auxiliary and full conditional phrase composition remain unresolved; this lexical correction does not certify trigger grammar."
},
{
"locale": "eu",
"key": "r-unarchived",
"before": "Biltegitik berreskuratu da",
"after": "Artxibotik berreskuratu da",
"reason": "Replace generic storage biltegi with archive artxibo, consistent with reviewed Basque activity/search archive terminology and EHU archive dictionary https://www.ehu.eus/eeh/cgi/bila?h=artxibo. Retain archive versus restore direction. Shared r-is Spanish auxiliary and full conditional phrase composition remain unresolved; this lexical correction does not certify trigger grammar."
}
]
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
appTitle = (defaultText = "Wekan"),
# The name of the app as it is displayed to the user.
appVersion = 1177,
appVersion = 1178,
# Increment this for every release.
appMarketingVersion = (defaultText = "11.77~2026-09-14"),
appMarketingVersion = (defaultText = "11.78~2026-09-14"),
# Human-readable presentation of the app version.
minUpgradableAppVersion = 0,
+4 -4
View File
@@ -19,7 +19,7 @@
# base: core24 (not core26) · no build-base: devel · grade: stable
# ─────────────────────────────────────────────────────────────────────────────
name: wekan
version: '11.77'
version: '11.78'
base: core24
confinement: strict
grade: stable
@@ -693,18 +693,18 @@ parts:
armhf) WEKAN_ARCH="armhf" ;;
*) echo "Unsupported architecture: ${CRAFT_ARCH_BUILD_FOR}"; exit 1 ;;
esac
WEKAN_ZIP="wekan-11.77-${WEKAN_ARCH}.zip"
WEKAN_ZIP="wekan-11.78-${WEKAN_ARCH}.zip"
echo "Cleaning environment first"
rm -rf .build
mkdir .build
cd .build
#wget https://github.com/wekan/wekan/releases/download/v11.77/wekan-11.77-amd64.zip
#wget https://github.com/wekan/wekan/releases/download/v11.78/wekan-11.78-amd64.zip
# Retry: a release asset can 404 for a short while right after it is
# uploaded (CDN/propagation lag), and a plain wget treated that as
# fatal. --retry-on-http-error covers the 404/5xx window; a genuinely
# missing bundle still fails after the tries are spent.
wget --tries=20 --waitretry=20 --retry-on-http-error=404,403,500,502,503 \
"https://github.com/wekan/wekan/releases/download/v11.77/${WEKAN_ZIP}"
"https://github.com/wekan/wekan/releases/download/v11.78/${WEKAN_ZIP}"
unzip "${WEKAN_ZIP}"
rm "${WEKAN_ZIP}"
# Copy to Snap. Use the bundle's OWN arch-correct Node.js: the
+27
View File
@@ -44,3 +44,30 @@ test('activity sanitizer handles malformed script end tags', () => {
}
assert.doesNotMatch(fs.readFileSync(__filename, 'utf8'), /sanitizeHTML:\s*value\s*=>\s*value\.replace/);
});
// Use the actual formatter so literal placeholders cannot pass a mocked API.
test('activity messages substitute all values and discard Spacebars options', async () => {
const i18next = require('i18next');
const sprintf = require('i18next-sprintf-postprocessor');
const i18n = i18next.createInstance().use(sprintf);
await i18n.init({ lng: 'en', resources: { en: { translation: {
added: "added label '%s' to %s", moved: 'moved %s from %s to %s',
comment: 'on %s', deleted: 'deleted an attachment from %s',
} } }, postProcess: ['sprintf'], interpolation: { escapeValue: false } });
let plainLinks = false;
const body = source.split(' activityMessage(key, ...values) {')[1].split('\n },')[0];
const helper = new Function('ReactiveCache', 'TAPi18n', 'sanitizeHTML', `return function(key, ...values) {${body}}`)(
{ getCurrentSetting: () => ({ renderLinksAsPlainText: plainLinks }) },
{ __: (key, options) => i18n.t(key, options) },
(value, options) => sanitizeHTML(value, { allowedTags: options.stripLinks ? ['strong', 'em'] : ['a', 'strong', 'em'], allowedAttributes: { a: ['href'] } }),
);
const link = '<a href="/card">Demo 👍</a>';
assert.equal(helper('added', '<em>Feature</em>', link, { hash: {} }), "added label '<em>Feature</em>' to " + link);
assert.equal(helper('moved', 'CARD', 'OLD', 'NEW', { hash: {} }), 'moved CARD from OLD to NEW');
assert.equal(helper('comment', link, { hash: {} }), 'on ' + link);
assert.equal(helper('deleted', 'CARD', { hash: {} }), 'deleted an attachment from CARD');
assert.equal(helper('comment', '100% safe %s', { hash: {} }), 'on 100% safe %s');
assert.doesNotMatch(helper('comment', '<script>bad()</script><strong>Good</strong>', { hash: {} }), /script|bad/);
plainLinks = true;
assert.equal(helper('comment', link, { hash: {} }), 'on Demo 👍');
});
@@ -9,7 +9,7 @@ const examples = ['== != <= >= && || ( )', 'Field1 == Value1', "'Field 1' == 'Va
(async () => {
const { repairProgress } = await import('../releases/translations/audit-progress.mjs');
const result = repairProgress();
for (const locale of ['ar', 'ar-DZ', 'ar-EG', 'lt', 'mn', 'el', 'el-GR', 'sk']) {
for (const locale of ['ar', 'ar-DZ', 'ar-EG', 'lt', 'mn', 'el', 'el-GR', 'sk', 'eo']) {
const data = read(locale);
const help = data['advanced-filter-description'];
assert.notEqual(help, source, locale);
@@ -25,6 +25,8 @@ const examples = ['== != <= >= && || ( )', 'Field1 == Value1', "'Field 1' == 'Va
assert.doesNotMatch(data['import-board-instruction-trello'], /In your Trello board/);
}
}
assert.match(read('eo')['advanced-filter-description'], /specialajn signojn.*literalajn signojn/);
assert.doesNotMatch(read('eo')['advanced-filter-description'], /regsignojn|Kampo1|Valoro1/);
// Syntax-only repair: Finnish prose remains an open Veps language finding.
const vepsHelp = read('ve-PP')['advanced-filter-description'];
for (const example of examples) {
@@ -2946,3 +2946,66 @@ const tokens = value => [...value.matchAll(/__[A-Za-z0-9]+(?:_[A-Za-z0-9]+)*__|%
require('node:assert/strict').match(da.Node_heap_malloced_memory, /hukommelse allokeret med malloc$/);
require('node:assert/strict').notEqual(da.Node_heap_malloced_memory, 'Node heap: allokeret hukommelse');
}
// Galician automation date triggers must cover both first setting and changes.
{
const gl = JSON.parse(require('node:fs').readFileSync(require('node:path').join(__dirname, '../imports/i18n/data/gl.i18n.json'), 'utf8'));
const assert = require('node:assert/strict');
const kinds = { due: 'vencemento', start: 'inicio', end: 'fin', received: 'recepción' };
for (const [kind, noun] of Object.entries(kinds)) {
const value = gl[`r-when-a-${kind}-date-changed`];
assert.equal(value, `Cando se establece ou cambia a data de ${noun}`);
assert.doesNotMatch(value, /início|Fim|Recebido|limite/);
}
}
// Clearing a Galician card field value must not claim its definition was deleted.
{
const gl = JSON.parse(require('node:fs').readFileSync(require('node:path').join(__dirname, '../imports/i18n/data/gl.i18n.json'), 'utf8'));
const assert = require('node:assert/strict');
assert.equal(gl['activity-unset-customfield'], "quitou o valor do campo personalizado '%s' en %s");
assert.notEqual(gl['activity-unset-customfield'], "quitou o campo personalizado '%s' en %s");
}
{
const gl = JSON.parse(require('node:fs').readFileSync(require('node:path').join(__dirname, '../imports/i18n/data/gl.i18n.json'), 'utf8'));
const assert = require('node:assert/strict');
assert.equal(gl.Node_heap_does_zap_garbage, 'Memoria heap de Node: sobrescribe o lixo cun patrón de bits');
assert.notEqual(gl.Node_heap_does_zap_garbage, 'Memoria heap de Node: limpa o lixo (zap garbage)');
}
// Received date captions describe a feminine Galician tarxeta.
{
const gl = JSON.parse(require('node:fs').readFileSync(require('node:path').join(__dirname, '../imports/i18n/data/gl.i18n.json'), 'utf8'));
const assert = require('node:assert/strict');
assert.equal(gl['card-received'], 'Recibida');
assert.equal(gl['card-received-on'], 'Recibida o');
assert.notEqual(gl['card-received'], 'Recibido');
assert.notEqual(gl['card-received-on'], 'Recibido o');
}
// Completed subtask tooltip agrees with feminine Galician subtarefa.
{
const gl = JSON.parse(require('node:fs').readFileSync(require('node:path').join(__dirname, '../imports/i18n/data/gl.i18n.json'), 'utf8'));
const assert = require('node:assert/strict');
assert.equal(gl.completed, 'Completada');
assert.notEqual(gl.completed, 'Completado');
}
// Basque archived activity states use archive terminology, not generic storage.
{
const eu = JSON.parse(require('node:fs').readFileSync(require('node:path').join(__dirname, '../imports/i18n/data/eu.i18n.json'), 'utf8'));
const assert = require('node:assert/strict');
for (const kind of ['Board', 'Card', 'List', 'Swimlane']) {
assert.match(eu['act-archived' + kind], /artxibora eraman da/);
assert.doesNotMatch(eu['act-archived' + kind], /biltegira/);
}
}
// Basque all-status search distinguishes archiving from generic storage.
{
const eu = JSON.parse(require('node:fs').readFileSync(require('node:path').join(__dirname, '../imports/i18n/data/eu.i18n.json'), 'utf8'));
const assert = require('node:assert/strict');
assert.equal(eu['globalSearch-instructions-status-all'], '`__predicate_all__` - artxibatutako eta artxibatu gabeko txartel guztiak');
assert.doesNotMatch(eu['globalSearch-instructions-status-all'], /biltegiratu/);
}
+12
View File
@@ -0,0 +1,12 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const test = require('node:test');
test('Basque rule labels distinguish archiving from storage and restoration direction', () => {
const data = JSON.parse(fs.readFileSync('imports/i18n/data/eu.i18n.json', 'utf8'));
assert.equal(data['r-archived'], 'Artxibora eraman da');
assert.equal(data['r-unarchived'], 'Artxibotik berreskuratu da');
for (const key of ['r-archived', 'r-unarchived']) assert.doesNotMatch(data[key], /bilteg/i);
const template = fs.readFileSync('client/components/rules/triggers/boardTriggers.jade', 'utf8');
assert.match(template, /value="archived".*r-archived/);
assert.match(template, /value="unarchived".*r-unarchived/);
});
+23
View File
@@ -0,0 +1,23 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const test = require('node:test');
const read = file => fs.readFileSync(file, 'utf8');
test('shared board content fills its available width without padding overflow', () => {
const css = read('client/components/boards/statsView.css');
const content = css.match(/\.stats-view-content\s*\{([^}]+)\}/)[1];
assert.match(content, /width:\s*100%/);
assert.match(content, /box-sizing:\s*border-box/);
assert.match(content, /max-width:\s*none/);
assert.match(content, /margin:\s*0\s*;/);
for (const view of ['timeline', 'time', 'stats', 'groupByAssignee']) {
assert.match(read(`client/components/boards/${view}View.jade`), /\.stats-view-content/);
}
});
test('chart shells cannot restore a fixed-width centered board layout', () => {
const css = read('client/components/boards/charts/boardCharts.css');
const content = css.match(/\.chart-view-content\s*\{([^}]+)\}/)[1];
assert.match(content, /max-width:\s*none/);
assert.doesNotMatch(content, /max-width:\s*\d+(?:px|rem)|margin:\s*0\s+auto/);
});
+7 -2
View File
@@ -22,14 +22,14 @@ function block(sel) {
test('language popup is responsively wide (not a fixed 450px)', () => {
const p = block(".pop-over[data-popup='changeLanguagePopup']");
assert.ok(/width:\s*min\(90vw,\s*1100px\)/.test(p), 'responsive width up to 1100px');
assert.ok(/width:\s*100vw/.test(p), 'full viewport width');
assert.ok(!/max-width:\s*450px/.test(p), 'no longer capped at 450px');
});
test('language list is an auto-filling multi-column grid', () => {
const list = block(".pop-over[data-popup='changeLanguagePopup'] .pop-over-list");
assert.ok(/display:\s*grid/.test(list), 'grid layout');
assert.ok(/grid-template-columns:\s*repeat\(auto-fill,\s*minmax\(170px,\s*1fr\)\)/.test(list),
assert.ok(/grid-template-columns:\s*repeat\(auto-fill,\s*minmax\(min\(100%,\s*280px\),\s*1fr\)\)/.test(list),
'auto-fill columns (many when wide, one when narrow)');
});
@@ -87,3 +87,8 @@ assert.match(jade, /span\.language-region[\s\S]*?language-country-flag[^\n]*\{\{
assert.doesNotMatch(jade, /language-flags[^\n]*countryFlag/);
assert.match(jade, /dir="\{\{languageDirection\}\}"/);
console.log(`changeLanguageColumns: all ${rows.length} popup locales and separate language/region flag placement verified`);
assert.match(block(".pop-over[data-popup='changeLanguagePopup'] .language-label"), /flex-direction: column/);
assert.match(block(".pop-over[data-popup='changeLanguagePopup'] .pop-over-list li > a"), /overflow: visible/);
assert.match(block(".pop-over[data-popup='changeLanguagePopup'] .pop-over-list li > a"), /white-space: normal/);
assert.match(jade, /span\.language-main-line\n\s+span\.emoji-icon\.language-flags/);
+2 -3
View File
@@ -306,8 +306,7 @@ test('no CHANGELOG section carries a Platform/Binary/From/Version/SHA256 table (
// Tried twice - right under the "In short" summary, then under a "Binaries
// in these bundles" label at the end of the section - and removed both
// times: it made every release's entry mostly a giant table nobody read.
// That data now lives only in the GitHub Release notes, built fresh by
// releases/provenance-table.sh every time a release is made. See CLAUDE.md.
// Provenance stays in build artifacts; it is also excluded from release notes.
assert.ok(!/^\| Platform \| Binary \| From \| Version \| SHA256 \|$/m.test(changelog),
'a Platform/Binary/From/Version/SHA256 table must not be in CHANGELOG.md');
assert.ok(!/^\*\*Binaries in these bundles:\*\*$/m.test(changelog),
@@ -404,7 +403,7 @@ test('a RELEASED section never claims its binaries were not rebuilt', () => {
`${version} carries the Upcoming section's "nothing here rebuilds them" - ` +
`a release rebuilds every bundle, so its table has to be that build's own ` +
`provenance (releases/provenance-table.sh prints it from provenance.tsv, ` +
`and the same table heads the GitHub release notes)`);
`kept separately in build artifacts)`);
});
test('CLAUDE.md states these rules, so they are not folklore', () => {
+32
View File
@@ -0,0 +1,32 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const test = require('node:test');
const source = fs.readFileSync('client/components/gantt/frappeGantt.js', 'utf8');
const body = source.slice(source.indexOf('function translateFrappeChrome('), source.indexOf('Template.frappeGanttView.onCreated'));
for (const translated of [false, true]) {
test(`Frappe chrome observer settles without a mutation loop (translated=${translated})`, () => {
let mutations = 0;
let callback;
const node = text => ({
get textContent() { return text; },
set textContent(value) { text = value; mutations++; },
});
const today = node('Today');
const mode = node('Mode');
const container = { querySelector: selector => selector === '.today-button' ? today : mode };
const instance = {};
class Observer {
constructor(fn) { callback = fn; }
observe() {}
}
const apply = new Function('TAPi18n', 'MutationObserver', `${body}; return translateFrappeChrome;`)(
{ __: key => translated ? (key === 'today' ? 'Tänään' : 'Tila') : (key === 'today' ? 'Today' : 'Mode') }, Observer);
apply(container, instance);
assert.equal(mutations, translated ? 2 : 0);
const first = mutations;
for (let i = 0; i < 10; i++) callback();
assert.equal(mutations, first, 'observer must not write identical text');
assert.equal(today.textContent, translated ? 'Tänään' : 'Today');
assert.match(source, /chromeObserver\.disconnect\(\)/);
});
}
+28
View File
@@ -0,0 +1,28 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { spawnSync } = require('node:child_process');
const test = require('node:test');
const source = fs.readFileSync('build.sh', 'utf8');
const ready = source.slice(source.indexOf('function git_operation_ready(){'), source.indexOf('function git_pull(){'));
for (const state of ['rebase-merge', 'rebase-apply', 'MERGE_HEAD', 'index.lock']) {
test(`existing ${state} stops the menu before remote operations`, () => {
const result = spawnSync('bash', ['-c', `${ready}
git() { case "$*" in "rev-parse --git-path ${state}") echo build.sh;; "rev-parse --git-path "*) echo .tools/tmp/nonexistent-git-menu-state;; *) echo main;; esac; }
git_operation_ready
`], { encoding: 'utf8' });
assert.equal(result.status, 1);
assert.match(result.stdout, new RegExp(state));
});
}
test('clean branch permits operation and cleanup failures cannot claim success', () => {
const result = spawnSync('bash', ['-c', `${ready}
git() { case "$*" in "rev-parse --git-path "*) echo .tools/tmp/nonexistent-git-menu-state;; *) echo main;; esac; }
git_operation_ready
`], { encoding: 'utf8' });
assert.equal(result.status, 0);
assert.match(source, /merge --no-edit/);
assert.match(source, /git stash store -m 'Recovered build.sh interrupted pull autostash'/);
assert.match(source, /git_recover_orphan_autostash \|\| return 1/);
assert.match(source, /merge.autoStash=true merge --ff-only/);
assert.doesNotMatch(source, /git fetch origin "\$branch" >\/dev\/null 2>&1 \|\| true/);
});
+44
View File
@@ -0,0 +1,44 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const source = fs.readFileSync(path.join(__dirname, '../packages/wekan-ldap/server/ldap.js'), 'utf8');
const body = source.split(' getUserFilter(username) {')[1].split('\n async bindUserIfNecessary')[0].replace(/\n }\s*$/, '');
const getUserFilter = new Function('escapedToHex', 'Log', `return function(username) {${body}}`)(value => value, { error() {} });
for (const value of [undefined, null, '']) {
assert.equal(getUserFilter.call({ options: { User_Search_Filter: value, User_Search_Field: 'sAMAccountName' } }, 'alice'), '(&(sAMAccountName=alice))');
}
for (const value of ['objectClass=person', '(objectClass=person)']) {
assert.equal(getUserFilter.call({ options: { User_Search_Filter: value, User_Search_Field: 'uid,mail' } }, 'alice'), '(&(objectClass=person)(|(uid=alice)(mail=alice)))');
}
for (const value of [true, 123, {}]) {
assert.throws(() => getUserFilter.call({ options: { User_Search_Filter: value, User_Search_Field: 'uid' } }, 'alice'), /LDAP_USER_SEARCH_FILTER must be a string/);
}
console.log('ldapOptionalUserFilter: unset optional filter, configured restrictions and invalid-type failures verified');
// Reproduce the actual post-bind search sequence from the LDAP implementation.
(async () => {
const AsyncFunction = Object.getPrototypeOf(async function() {}).constructor;
// These methods use only the established structured logger as an external dependency.
const Log = { info() {}, debug() {} };
for (const filter of [undefined, null, '', '(objectClass=person)']) {
const events = [];
const ldap = {
options: { Authentication: true, Authentication_UserDN: 'service@example.test', Authentication_Password: 'test-only-password', User_Search_Filter: filter, User_Search_Field: 'sAMAccountName' },
getUserFilter, getUserAttributes: () => undefined,
bind: async (dn) => { assert.equal(dn, 'service@example.test'); events.push('bind'); },
searchAll: async (base, options) => { events.push('search'); assert.equal(options.filter, filter ? '(&(objectClass=person)(sAMAccountName=alice))' : '(&(sAMAccountName=alice))'); return [{dn:'cn=alice'}]; },
};
const bindBody = source.split(' async bindIfNecessary() {')[1].split('\n async searchUsers(')[0].replace(/\n }\s*$/, '');
const searchBody = source.split(' async searchUsers(username) {')[1].split('\n async getUserById(')[0].replace(/\n }\s*$/, '');
ldap.bindIfNecessary = new AsyncFunction('Log', bindBody).bind(ldap, Log);
ldap.searchUsers = new AsyncFunction('Log', 'username', searchBody).bind(ldap, Log);
assert.deepEqual(await ldap.searchUsers('alice'), [{dn:'cn=alice'}]);
assert.deepEqual(events, ['bind','search']);
ldap.domainBinded = false; events.length = 0;
ldap.bind = async () => { throw new Error('invalid credentials'); };
await assert.rejects(ldap.searchUsers('alice'), /invalid credentials/);
assert.equal(events.length, 0, 'failed bind must never proceed to search');
}
console.log('ldapOptionalUserFilter: actual bind/search sequence and failed-bind isolation verified');
})().catch(error => { console.error(error); process.exitCode = 1; });
+2 -2
View File
@@ -144,12 +144,12 @@ test('destination restart reuses the completed source and skips completed mirror
const manifest={diskSnapshot:1,base:f.base,complete:true,issueFiles:[],releaseFiles:[]};
fs.mkdirSync(f.base,{recursive:true});fs.writeFileSync(path.join(f.base,'source-manifest.json'),JSON.stringify(manifest));
}
if(args.some(a=>a.endsWith('mirror-codeberg.sh')))throw Error('interrupted destination fixture');
if(args.includes('--target')&&args[args.indexOf('--target')+1]==='codeberg'&&!args.includes('--git-only'))throw Error('interrupted destination fixture');
};
assert.equal(await menu.sync(settings,{directory:f.directory,run,log:()=>{}}),false);
calls.length=0;
assert.equal(await menu.sync(settings,{directory:f.directory,run:async(_,args)=>{calls.push(args);},log:()=>{}}),true);
assert.equal(calls.length,1);assert.ok(calls[0][0].endsWith('mirror-codeberg.sh'));
assert.equal(calls.length,3);assert.ok(calls.slice(0,2).every(args=>args.includes('--git-only')));assert.ok(calls[2].includes('codeberg'));assert.ok(!calls.some(args=>args.includes('--export-source')));
assert.equal(JSON.parse(fs.readFileSync(path.join(f.base,'sync-progress.json'))).complete,true);
} finally {fs.rmSync(f.directory,{recursive:true,force:true});}
});
+35
View File
@@ -0,0 +1,35 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const temporary = path.resolve(__dirname,'../.tools/tmp');
fs.mkdirSync(temporary,{recursive:true});
test('missing and network-failed links recover closest creation-date capture with provenance',async()=>{
const {downloadHistoricalUrl}=await import('../tools/mirror-archive.mjs');
const directory=fs.mkdtempSync(path.join(temporary,'mirror-history-'));
try {for(const failed of [false,true]){
const calls=[],events=[];
const recovered=path.join(directory,'recovered.html');fs.writeFileSync(recovered,'historical page');
const result=await downloadHistoricalUrl('https://example.com/lost',{temporary:directory,allLinks:true,createdAt:'2020-02-03T04:05:06Z'},async url=>{
calls.push(url);
if(calls.length===1){if(failed)throw Error('network timeout');return {missing:true};}
if(calls.length===2){assert.equal(new URL(url).searchParams.get('timestamp'),'20200203040506');const file=path.join(directory,'lookup.json');fs.writeFileSync(file,JSON.stringify({archived_snapshots:{closest:{available:true,status:'200',timestamp:'20200203040600',url:'http://web.archive.org/web/20200203040600/https://example.com/lost'}}}));return {file};}
return {file:recovered,originalName:'lost.html'};
},(status,detail)=>events.push(status));
assert.equal(result.file,recovered);assert.match(result.recoveredFrom,/^https:\/\/web.archive.org/);assert.equal(result.captureTimestamp,'20200203040600');assert.deepEqual(events,['checking','recovered']);assert.equal(calls.length,3);
}}finally{fs.rmSync(directory,{recursive:true,force:true});}
});
test('live links bypass history and unsafe links never reach archive.org',async()=>{
const {downloadHistoricalUrl}=await import('../tools/mirror-archive.mjs');let calls=0;
assert.deepEqual(await downloadHistoricalUrl('https://example.com/',{createdAt:'2020-01-01'},async()=>{calls++;return {file:'live'};}),{file:'live'});assert.equal(calls,1);
calls=0;await assert.rejects(downloadHistoricalUrl('http://127.0.0.1/',{createdAt:'2020-01-01'},async()=>{calls++;throw Error('Linked URL does not resolve exclusively to public Internet addresses');}),/public Internet/);assert.equal(calls,1);
});
test('optional attachment rate limits defer without sleeping or violating saved cooldown',async()=>{
const {createLimiter}=await import('../tools/mirror-rate-limits.mjs');const directory=fs.mkdtempSync(path.join(temporary,'mirror-history-rate-'));let slept=0,requests=0;
try {const stateFile=path.join(directory,'state.json');const limiter=createLimiter({stateFile,now:()=>1000,pause:async()=>{slept++;},log:()=>{}});
await assert.rejects(limiter.perform('example.com',async()=>{requests++;return new Response('',{status:429,headers:{'retry-after':'90'}});},{maxWaitMs:3000,noRetry:true}),/deferred/);
await assert.rejects(limiter.perform('example.com',async()=>{requests++;},{maxWaitMs:3000,noRetry:true}),/deferred/);
assert.equal(requests,1);assert.equal(slept,0);assert.ok(JSON.parse(fs.readFileSync(stateFile)).hosts['example.com']>=91000);
}finally{fs.rmSync(directory,{recursive:true,force:true});}
});
+37 -9
View File
@@ -48,11 +48,12 @@ const work = fs.mkdtempSync(path.join(temporary, 'mirror-menu-tests-'));
await test('shared snapshot and per-target dispatch continue after failures; preview never applies', async () => {
const settings = { source: 'gitlab', mirrors: ['github', 'codeberg', 'sourceforge'] };
const calls = [];
assert.equal(await m.sync(settings, { directory: work, preview: true, log: () => {}, run: (tool, args) => { calls.push([tool, args]); if (args.some(a => a.endsWith('mirror-codeberg.sh'))) throw new Error('fixture failure'); return ''; } }), false);
assert.equal(calls.length, 5);
assert.equal(await m.sync(settings, { directory: work, preview: true, log: () => {}, run: (tool, args) => { calls.push([tool, args]); if (args.includes('--target') && args[args.indexOf('--target')+1] === 'codeberg' && !args.includes('--git-only')) throw new Error('fixture failure'); return ''; } }), false);
assert.equal(calls.length, 8);
assert.ok(calls.every(c => !c[1].includes('--apply')));
assert.ok(calls[0][1].includes('--export-source'));
assert.ok(calls[4][1][0].endsWith('mirror-sourceforge.sh'));
assert.ok(calls.slice(0,3).every(c=>c[1].includes('--git-only')));
assert.ok(calls[3][1].includes('--export-source'));
assert.ok(calls[7][1].includes('sourceforge'));
const snapshots = calls.filter(c => c[1].includes('--snapshot')).map(c => c[1][c[1].indexOf('--snapshot') + 1]);
assert.equal(new Set(snapshots).size, 1);
await assert.rejects(m.sync({ source: 'github', mirrors: [] }, { directory: work }), /No active/);
@@ -61,7 +62,34 @@ const work = fs.mkdtempSync(path.join(temporary, 'mirror-menu-tests-'));
assert.equal(invoked, 1, 'failed source inventory stops before destination operations');
const archiveFailure = [];
assert.equal(await m.sync(settings, { directory: work, log: () => {}, run: (tool, args) => { archiveFailure.push(args); if (args.includes('--archive-only')) throw new Error('disk failure'); return ''; } }), false);
assert.equal(archiveFailure.length, 5, 'archive failures are reported while every destination still runs');
assert.equal(archiveFailure.length, 5, 'archive failure stops content writes to destinations');
});
await test('Git precedes collection; target content overlaps with counters and separate logs', async () => {
const snapshot = path.join(work, 'parallel-source.json');
fs.writeFileSync(snapshot, JSON.stringify({issues:[{}], releases:[{}]}));
const logFile = path.join(work, 'parallel-logs', 'mirror-log.txt');
let active = 0, peak = 0;
const calls = [], output = [];
await m.sync({source:'gitlab', mirrors:['github','codeberg','sourceforge']}, {
directory:work, sourceSnapshotFile:snapshot,
environment:{WEKAN_MIRROR_LOG_FILE:logFile}, log:line=>output.push(line),
run: async (_, args, input, options) => {
calls.push(args);
if (args.includes('--git-only') || args.includes('--archive-only')) return '';
active++; peak = Math.max(peak,active);
const target = args[args.indexOf('--target')+1];
assert.equal(path.basename(options.mirrorLog), target+'.txt');
assert.ok(fs.existsSync(options.mirrorLog));
options.onOutput(`[${target}] processed: issue 1\n[${target}] processed: release 1\n`);
await new Promise(resolve=>setTimeout(resolve,10)); active--;
return '';
},
});
assert.equal(peak,3);
assert.ok(calls.slice(0,3).every(args=>args.includes('--git-only')));
assert.ok(calls[3].includes('--archive-only'));
assert.ok(calls.slice(4).every(args=>args.includes('--skip-archive')&&!args.includes('--code')));
assert.ok(output.some(line=>line.includes('github:')&&line.includes('codeberg:')&&line.includes('sourceforge:')&&line.includes('issues 1/1')&&line.includes('releases 1/1')));
});
await test('progress precedes asynchronous work and failures do not report success', async () => {
const output = [];
@@ -70,8 +98,8 @@ const work = fs.mkdtempSync(path.join(temporary, 'mirror-menu-tests-'));
run: async (_, args) => {
if (args.includes('--export-source')) assert.match(output.at(-1), /Reading source/);
if (args.includes('--archive-only')) assert.match(output.at(-1), /Updating local archive/);
if (args.some(a => a.endsWith('mirror-gitlab.sh'))) {
assert.match(output.at(-1), /Syncing GitLab/);
if (args.includes('--target') && !args.includes('--git-only')) {
assert.match(output.at(-1), /Started/);
throw new Error('async fixture failure');
}
await Promise.resolve();
@@ -120,8 +148,8 @@ const work = fs.mkdtempSync(path.join(temporary, 'mirror-menu-tests-'));
const directory = path.join(work, 'checkout spaces & %PATH% !value');
await m.sync({ source: 'gitlab', mirrors: ['github'] }, { platform: 'win32', directory, run: (tool, args) => { calls.push([tool, args]); return ''; }, log: () => {} });
assert.ok(calls.every(([tool]) => tool === process.execPath));
assert.deepEqual(calls[2][1].slice(0, 5), [path.join(directory, 'tools/mirror-active-forges.mjs'), '--target', 'github', '--code', '--apply']);
assert.ok(calls[2][1].includes('gitlab'));
assert.deepEqual(calls[3][1].slice(0, 4), [path.join(directory, 'tools/mirror-active-forges.mjs'), '--target', 'github', '--apply']);
assert.ok(calls[3][1].includes('gitlab'));
const previews = [];
await m.sync({ source: 'gitlab', mirrors: ['github'] }, { platform: 'win32', directory, preview: true, run: (tool, args) => { previews.push([tool, args]); return ''; }, log: () => {} });
assert.ok(previews.every(([, args]) => !args.includes('--apply')));
+7 -21
View File
@@ -132,28 +132,14 @@ test('Node.js is resolved per architecture, not fetched from a fixed URL', () =>
'it still knows all three sources');
});
test('the provenance table is what makes a `latest` build reproducible after the fact', () => {
// `latest` means rebuilding an old release would embed a different FerretDB
// than it shipped with. That is a deliberate trade - security fixes arrive
// without a commit - and what makes it safe is that every release RECORDS the
// versions and SHA256s it actually shipped.
//
// That record lives in the GitHub Release notes, not CHANGELOG.md: the
// Platform/Binary/From/Version/SHA256 table used to be duplicated into
// CHANGELOG.md as well, but the maintainer removed it there (it made every
// release's changelog entry mostly a giant table nobody read), so the
// release notes - built fresh from provenance-table.sh for every build -
// are now the one place this answers "which FerretDB did v10.77 ship?".
const rec = 'releases/record-provenance.sh';
assert.ok(fs.existsSync(path.join(repoRoot, rec)),
`${rec} must exist: it is what answers "which FerretDB did v10.77 ship?"`);
const table = 'releases/provenance-table.sh';
assert.ok(fs.existsSync(path.join(repoRoot, table)), `${table} must exist`);
test('binary provenance stays separate from release notes', () => {
assert.ok(fs.existsSync(path.join(repoRoot, 'releases/record-provenance.sh')));
const workflow = fs.readFileSync(path.join(repoRoot, '.github/workflows/release-all.yml'), 'utf8');
const uses = workflow.match(/bash releases\/provenance-table\.sh > release-notes\.md/g) || [];
assert.ok(uses.length >= 2,
'and release-all.yml still puts it at the top of the release notes (the initial '
+ 'release and the later notes rewrite)');
assert.ok(/pattern: provenance-/.test(workflow), 'build provenance artifacts remain available');
assert.ok(!/bash releases\/provenance-table\.sh/.test(workflow),
'neither initial nor refreshed release notes may include the provenance table');
const uses = workflow.match(/bash releases\/release-notes\.sh "\$VERSION" > release-notes\.md/g) || [];
assert.equal(uses.length, 2, 'initial and refreshed notes use only changelog text');
});
console.log(`\n${passed} tests passed`);
@@ -14,6 +14,14 @@ for (const direction of ['ltr', 'rtl']) {
await boardPage.locator('.js-change-language').click();
const popup = boardPage.locator(".pop-over[data-popup='changeLanguagePopup']");
await expect(popup).toBeVisible();
const bounds = await popup.boundingBox();
expect(bounds.x).toBeCloseTo(0, 0);
expect(bounds.width).toBeCloseTo(boardPage.viewportSize().width, 0);
for (const width of [1280, 375]) {
await boardPage.setViewportSize({width,height:900});
expect(await popup.locator('.js-set-language').evaluateAll(entries => entries.every(entry => entry.scrollWidth <= entry.clientWidth + 1))).toBe(true);
}
await boardPage.setViewportSize({width:1280,height:900});
await expect(popup.locator('.js-set-language')).toHaveCount(rows.length);
for (const row of rows) {
await expect(popup.locator(`.js-set-language[data-language="${row[2]}"]`)).toHaveCount(1);
@@ -40,11 +48,11 @@ for (const direction of ['ltr', 'rtl']) {
]);
if (direction === 'ltr') {
expect(flag.x).toBeLessThan(name.x);
expect(name.x).toBeLessThan(region.x);
expect(region.y).toBeGreaterThan(name.y);
expect(country.x).toBeLessThan(regionName.x);
} else {
expect(flag.x).toBeGreaterThan(name.x);
expect(name.x).toBeGreaterThan(region.x);
expect(region.y).toBeGreaterThan(name.y);
expect(country.x).toBeGreaterThan(regionName.x);
}
});
@@ -33,6 +33,7 @@ for (const policy of ['formatted', 'plain-links', 'plain-source']) {
for (const root of [boardPage.locator('.board-sidebar'), cp.root]) {
const activity = root.locator(`.activity[data-id="${activityId}"]`);
await expect(activity).toBeVisible();
await expect(activity.locator('.activity-desc')).not.toContainText('%s');
await expect(activity.locator('[onerror], script')).toHaveCount(0);
expect(await boardPage.evaluate(() => window.activityViewerAttack)).toBeUndefined();
if (policy === 'plain-source') {
@@ -0,0 +1,17 @@
const { test, expect } = require('../fixtures');
for (const view of ['timeline', 'time', 'stats']) {
test(`${view} content spans the board at desktop and mobile widths`, async ({ boardPage }) => {
await boardPage.locator(`.js-open-${view}-view`).first().click();
const content = boardPage.locator('.stats-view-content').first();
await expect(content).toBeVisible();
for (const width of [1440, 375]) {
await boardPage.setViewportSize({ width, height: 900 });
await expect.poll(async () => content.evaluate(element => {
const box = element.getBoundingClientRect();
const parent = element.parentElement.getBoundingClientRect();
return Math.max(Math.abs(box.left - parent.left), Math.abs(box.right - parent.right));
})).toBeLessThanOrEqual(1);
}
});
}
@@ -0,0 +1,15 @@
const { test, expect } = require('../fixtures');
const db = require('../helpers/db');
test('English Frappe Gantt stays responsive and permits switching views', async ({ boardPage, board, user }) => {
db.updateOne('users', { _id: user.id }, { $set: { 'profile.language': 'en' } });
db.updateOne('cards', { boardId: board.boardId, title: 'Alpha Card' }, { $set: {
startAt: new Date('2026-09-14T12:00:00Z'), dueAt: new Date('2026-09-16T12:00:00Z'),
} });
await boardPage.reload();
await boardPage.locator('.js-open-gantt-frappe-view').first().click();
await expect(boardPage.locator('.js-frappe-gantt-container svg')).toBeVisible();
await boardPage.locator('.js-open-swimlanes-view').first().click();
await expect(boardPage.locator('.js-frappe-gantt-container')).toHaveCount(0);
await expect(boardPage.getByText('Alpha Card', { exact: true }).first()).toBeVisible();
});
+3 -3
View File
@@ -113,10 +113,10 @@ test('every step that writes the notes into release-notes.md reads them from the
!/>>\s*release-notes\.md[^\n]*\$\{\{/.test(text),
'a step interpolates ${{ }} into the notes, where a backtick runs as a command',
);
// and what must be there instead
// Notes now overwrite the file: provenance is excluded, while text stays data.
assert.ok(
/release-notes\.sh "\$VERSION" >> release-notes\.md/.test(text),
'expected the notes to be appended from releases/release-notes.sh',
/release-notes\.sh "\$VERSION" > release-notes\.md/.test(text),
'expected changelog-only notes written directly from releases/release-notes.sh',
);
});
+44
View File
@@ -0,0 +1,44 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const root = path.resolve(__dirname, '..');
const tmpRoot = path.join(root, '.tools/tmp');
fs.mkdirSync(tmpRoot, { recursive: true });
const dir = fs.mkdtempSync(path.join(tmpRoot, 'release-language-summary-'));
function run(text, version = '99.99') {
const file = path.join(dir, 'CHANGELOG.md');
fs.writeFileSync(file, text);
return spawnSync('bash', ['releases/release-notes.sh', version, file], {
cwd: root, encoding: 'utf8', env: { ...process.env, TMPDIR: dir },
});
}
try {
const intro = '# Status\nReference to `# Upcoming WeKan ® release` is not a heading.\n\n# Upcoming WeKan ® release\n\n**In short:** Summary.\n\n';
const translations = '**Translations** - Repairs.\n\n**Languages updated:** Galician, Esperanto, Galician\n\n<details>\n<summary>Private translation details</summary>\nand this is wrapped prose, not a section boundary.\n<details>nested details</details>\n</details>\n\n';
const other = 'and fixes the following bugs:\n\n**Authentication** - Login.\n\n<details>\n<summary>Keep non-translation detail</summary>\nSecurity fix.\n</details>\n\nThanks to above GitHub users.\n';
const result = run(intro + translations + other + '\n# v1.00 date WeKan ® release\nOld release.\n');
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /## Translations\n\n- Esperanto\n- Galician\n/);
assert.doesNotMatch(result.stdout, /Private translation details|wrapped prose|nested details|Reference to|Old release/);
assert.doesNotMatch(result.stdout, /Keep non-translation detail|Security fix/);
assert.deepEqual(result.stdout.match(/^## .+$/gm), ['## In short', '## Security', '## Translations']);
assert.match(result.stdout, /More details at ChangeLog/);
assert.equal((result.stdout.match(/- Galician/g) || []).length, 1);
const missing = run(intro + translations.replace('**Languages updated:** Galician, Esperanto, Galician\n', '') + other);
assert.notEqual(missing.status, 0);
assert.match(missing.stderr, /Translations group needs/);
const selected = run(intro + translations + other + '\n# v2.00 2026-09-14 WeKan ® release\n\n**In short:** Version-specific summary.\n', '2.00');
assert.equal(selected.status, 0, selected.stderr);
assert.match(selected.stdout, /Version-specific/);
assert.doesNotMatch(selected.stdout, /Galician|Private translation details/);
assert.match(selected.stdout, /CHANGELOG\.md#v200-2026-09-14-wekan--release/);
const security = run(intro + translations + 'This release fixes the following CRITICAL SECURITY ISSUES:\n\n**Security** - Board boundaries.\n\n<details>\n<summary>Security fix retained</summary>\nand wrapped security prose.\n</details>\n\n' + other);
assert.equal(security.status, 0, security.stderr);
assert.match(security.stdout, /Security fix retained|wrapped security prose/);
assert.doesNotMatch(security.stdout, /Keep non-translation detail|Private translation details/);
console.log('releaseTranslationSummary: languages only, nested details removed, only requested sections retained, explicit metadata required, headings anchored');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
+14 -1
View File
@@ -482,6 +482,7 @@ export async function syncIssues(adapter, snapshot, apply, record) {
if (apply && issue.state === 'closed' && (created || !closed)) await adapter.close(target);
record('checked', `issue ${issue.html_url}`);
} catch (error) { record('failed', `issue ${issue.html_url}: ${error.message}`); }
finally { record('processed', `issue ${issue.html_url}`); }
}
}
export async function syncReleases(adapter, snapshot, apply, download, record) {
@@ -515,6 +516,7 @@ export async function syncReleases(adapter, snapshot, apply, download, record) {
}
record('checked', `release ${release.tag_name}`);
} catch (error) { record('failed', `release ${release.tag_name}: ${error.message}`); }
finally { record('processed', `release ${release.tag_name}`); }
}
}
async function downloadAsset(asset, directory) {
@@ -623,6 +625,7 @@ export async function sourceForgeReleases(snapshot, apply, download, record, run
}
record('checked', `SourceForge release files ${release.tag_name}`);
} catch (error) { record('failed', `SourceForge release ${release.tag_name}: ${error.message}`); }
finally { record('processed', `release ${release.tag_name}`); }
}
}
export async function main(args = process.argv.slice(2)) {
@@ -642,10 +645,20 @@ export async function main(args = process.argv.slice(2)) {
else if (args[i] === '--source') { selectedSource = args[++i]; if (!Object.hasOwn(forges, selectedSource)) throw new Error('--source needs a known forge name'); }
else if (args[i] === '--snapshot') { snapshotFile = args[++i]; if (!snapshotFile) throw new Error('--snapshot needs a filename'); }
else if (args[i] === '--export-source') { exportFile = args[++i]; if (!exportFile) throw new Error('--export-source needs a filename'); }
else if (!['--apply', '--code', '--archive-only', '--skip-archive', '--incremental', '--cache-only'].includes(args[i])) throw new Error(`Unknown argument ${args[i]}`);
else if (!['--apply', '--code', '--archive-only', '--skip-archive', '--incremental', '--cache-only', '--git-only'].includes(args[i])) throw new Error(`Unknown argument ${args[i]}`);
}
const settings = loadSettings(root, activeMirrors(fs.readFileSync(path.join(root, 'releases/mirror.sh'), 'utf8')).map(m => m.name));
selectedSource ||= settings.source;
if (args.includes('--git-only')) {
for (const name of settings.mirrors.filter(name => name !== selectedSource && (!target || name === target))) {
console.log(`[${name}] Git sync started ${new Date().toISOString()}`);
if (args.includes('--apply')) syncGit({name, url:forges[name].push, sourceName:selectedSource});
else console.log(`[${name}] Git branches and tags planned`);
console.log(`[${name}] Git sync finished`);
}
return;
}
if (exportFile) {
if (snapshotFile || args.includes('--apply')) throw new Error('--export-source is read-only and cannot be combined with --apply or --snapshot');
if (incremental) {
+42 -10
View File
@@ -172,7 +172,8 @@ export async function pinnedFetch(url, options, addresses, transport = url.proto
}
export function commentIdentity(comment) {return staticCommentIdentity(comment);}
export async function downloadUrl(url, { temporary, previous, cachedFile, fetcher = fetch, allLinks = false, resolveHost = lookup } = {}) {
export async function downloadUrl(url, { temporary, previous, cachedFile, fetcher = fetch, allLinks = false, resolveHost = lookup, timeoutMs = 30000 } = {}) {
const signal = AbortSignal.timeout(timeoutMs);
const requested = new URL(url);
const archive = requested.hostname === 'api.github.com' && requested.pathname.match(/^\/repos\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/(zipball|tarball)\/(.+)$/);
// Use the file service directly so hundreds of public release archives do
@@ -188,10 +189,10 @@ export async function downloadUrl(url, { temporary, previous, cachedFile, fetche
for (let hop = 0; hop < 10; hop++) {
const u = new URL(url);
if (!['http:', 'https:'].includes(u.protocol) || (!allLinks && (u.protocol !== 'https:' || !allowed(u.hostname))) || u.username || u.password) throw new Error('Unsupported attachment redirect host');
const addresses = allLinks ? await publicLink(u, resolveHost) : undefined;
const requestOptions = { headers, redirect: 'manual', signal: AbortSignal.timeout(3600000) };
const operation=()=>{const options={...requestOptions,signal:AbortSignal.timeout(3600000)};return allLinks && fetcher === fetch ? pinnedFetch(u,options,addresses) : fetcher(u.href,options);};
response=fetcher===fetch?await limiter.perform(u.hostname,operation):await operation();
const addresses = allLinks ? await publicLink(u, (host,opts) => new Promise((resolve,reject)=>{signal.addEventListener('abort',()=>reject(signal.reason),{once:true});Promise.resolve(resolveHost(host,opts)).then(resolve,reject);} )) : undefined;
const requestOptions = { headers, redirect: 'manual', signal };
const operation=()=>{const options={...requestOptions,signal};return allLinks && fetcher === fetch ? pinnedFetch(u,options,addresses) : fetcher(u.href,options);};
response=fetcher===fetch?await limiter.perform(u.hostname,operation,{maxWaitMs:3000,noRetry:true}):await operation();
if (![301, 302, 303, 307, 308].includes(response.status)) break;
const location = response.headers.get('location');
if (!location) throw new Error('File redirect has no Location');
@@ -204,7 +205,8 @@ export async function downloadUrl(url, { temporary, previous, cachedFile, fetche
if (!response.ok || !response.body) throw new Error(`File download failed: HTTP ${response.status}`);
fs.mkdirSync(temporary, { recursive: true });
const file = path.join(temporary, `${randomUUID()}.part`);
await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(file));
try { await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(file), {signal}); }
catch (error) { fs.rmSync(file,{force:true}); throw error; }
const length = response.headers.get('content-length');
// Encoded responses are transparently decoded by fetch.
if (length !== null && !response.headers.get('content-encoding') && fs.statSync(file).size !== Number(length)) throw new Error('Incomplete attachment download');
@@ -219,6 +221,36 @@ export async function downloadUrl(url, { temporary, previous, cachedFile, fetche
return { file, etag: response.headers.get('etag') || undefined, lastModified: response.headers.get('last-modified') || undefined, originalName };
}
export async function downloadHistoricalUrl(url, options, fetchFile = downloadUrl, record = () => {}) {
let failure, live;
try { live = await fetchFile(url, options); if (!live.missing) return live; }
catch (error) {
if (/Unsupported|public Internet addresses|credential/i.test(error.message)) throw error;
failure = error;
}
if (!options.createdAt || !Number.isFinite(Date.parse(options.createdAt))) {
if (failure) throw failure;
return live;
}
const timestamp = new Date(options.createdAt).toISOString().replace(/\D/g,'').slice(0,14);
record('checking', `archive.org history for ${url} at ${timestamp}`);
const lookupUrl = `https://archive.org/wayback/available?url=${encodeURIComponent(url)}&timestamp=${timestamp}`;
const lookup = await fetchFile(lookupUrl, {temporary:options.temporary,allLinks:true});
if (lookup.missing) { if (failure) throw failure; return live; }
let data;
try { data = JSON.parse(fs.readFileSync(lookup.file,'utf8')); }
finally { if (!lookup.reused) fs.rmSync(lookup.file,{force:true}); }
const capture = data.archived_snapshots?.closest;
if (!capture?.available || String(capture.status) !== '200') { if (failure) throw failure; return live; }
const archived = new URL(capture.url);
if (!['http:','https:'].includes(archived.protocol) || archived.hostname !== 'web.archive.org' || archived.username || archived.password || !/^\/web\/\d{14}(?:[a-z_]+)?\//.test(archived.pathname)) throw Error('Unsupported archive.org capture URL');
archived.protocol = 'https:';
const result = await fetchFile(archived.href, {temporary:options.temporary,allLinks:true});
if (result.missing) { if (failure) throw failure; return live; }
record('recovered', `${url} from ${archived.href}`);
return {...result, recoveredFrom:archived.href, captureTimestamp:capture.timestamp};
}
export async function archiveItem(base, type, key, source, files, options) {
const { apply, record, downloadAsset, fetchFile, temporary, now } = options;
const directory = itemDirectory(base, type, key);
@@ -247,7 +279,7 @@ export async function archiveItem(base, type, key, source, files, options) {
if (descriptor.asset && (!descriptor.asset.sourceName || descriptor.asset.sourceName === 'github')) {
if (cached && old.assetId === descriptor.asset.id && old.size === descriptor.asset.size && old.digest === descriptor.asset.digest) fetched = { file: path.join(directory, old.name), reused: true };
else fetched = { file: await downloadAsset(descriptor.asset, temporary) };
} else fetched = await fetchFile(descriptor.source, { temporary, previous: old, cachedFile: cached ? path.join(directory, old.name) : undefined, allLinks: descriptor.allLinks === true });
} else { const downloadOptions = { temporary, previous: old, cachedFile: cached ? path.join(directory, old.name) : undefined, allLinks: descriptor.allLinks === true, createdAt:descriptor.createdAt }; fetched = descriptor.allLinks ? await downloadHistoricalUrl(descriptor.source,downloadOptions,fetchFile,record) : await fetchFile(descriptor.source,downloadOptions); }
if (fetched.missing) {
if (old && fs.existsSync(path.join(directory, old.name))) { retire(path.join(directory, old.name), now); record('retired', `${type}/${key}/${old.name}: source returned 404/410`); }
record('missing', `${descriptor.source}: source file is no longer available`); continue;
@@ -256,7 +288,7 @@ export async function archiveItem(base, type, key, source, files, options) {
descriptor.name = `attachment-${createHash('sha256').update(descriptor.source).digest('hex').slice(0, 8)}-${archiveName(fetched.originalName)}`;
file = path.join(directory, descriptor.name);
}
validators = { etag: fetched.etag, lastModified: fetched.lastModified, originalName: fetched.originalName };
validators = { etag: fetched.etag, lastModified: fetched.lastModified, originalName: fetched.originalName, ...(fetched.recoveredFrom ? {recoveredFrom:fetched.recoveredFrom,captureTimestamp:fetched.captureTimestamp} : {}) };
const hash = contentHash = fetched.reused && old ? old.sha256 : await sha256(fetched.file);
if (descriptor.asset && ((descriptor.asset.size !== undefined && fs.statSync(fetched.file).size !== descriptor.asset.size) || (descriptor.asset.digest && descriptor.asset.digest !== `sha256:${hash}`))) throw new Error('Source asset size/digest mismatch');
const same = fs.existsSync(file) && ((cached && old.name === descriptor.name && old.sha256 === hash) || await sha256(file) === hash);
@@ -280,7 +312,7 @@ export async function archiveItem(base, type, key, source, files, options) {
} catch (error) {
// A network or disk failure is not proof the file was removed.
if (old) current.push({ ...old, fetchFailed: true });
record('failed', `${type}/${key}/${descriptor.name}: ${error.message}`);
record(descriptor.allLinks ? 'skipped' : 'failed', `${type}/${key}/${descriptor.name}: ${error.message}`);
}
}
if (!apply) return;
@@ -356,7 +388,7 @@ async function archiveUnlockedSnapshot(snapshot, { root, apply = true, downloadA
const id = comment.id === 'body' ? 'body' : commentIdentity(comment);
commentIds.add(id);
const archivedComment = {...comment, archiveCommentId:id};
const linked = issueFiles({...comment, commentsToMirror:[], reviews:[], source_url:comment.source_url || issue.source_url || issue.html_url}, true).map(f=>({...f,allLinks:true}));
const linked = issueFiles({...comment, commentsToMirror:[], reviews:[], source_url:comment.source_url || issue.source_url || issue.html_url}, true).map(f=>({...f,allLinks:true,createdAt:comment.created_at || issue.created_at}));
await archiveItem(directory, 'comments', id, comment.html_url || issue.html_url, [
{key:'comment',name:'comment.json',kind:'metadata',bytes:serialized(archivedComment)},
{key:'listing',name:'index.html',kind:'metadata',bytes:Buffer.alloc(0)},
+41 -14
View File
@@ -24,8 +24,14 @@ export function streamCommand(tool, args, input, options = {}) {
// Only the running Node executable and the fixed Unix wrapper interpreter
// are needed. Checkout paths always remain arguments, never shell source.
if (tool !== process.execPath && tool !== 'bash') throw new Error('Unsupported mirror executable');
const spawnOptions = { ...options, shell: false, windowsHide: true, stdio: ['ignore', 'inherit', 'inherit'] };
const { mirrorLog, onOutput, ...childOptions } = options;
const spawnOptions = { ...childOptions, shell: false, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] };
const child = tool === 'bash' ? spawn('bash', args, spawnOptions) : spawn(process.execPath, args, spawnOptions);
for (const output of [child.stdout, child.stderr]) output.on('data', chunk => {
process.stdout.write(chunk);
if (mirrorLog) fs.appendFileSync(mirrorLog, chunk);
onOutput?.(chunk.toString());
});
child.once('error', reject);
child.once('close', (code, signal) => code === 0 ? resolve('') : reject(new Error(`Mirror command failed (${signal || code})`)));
});
@@ -52,11 +58,11 @@ export async function sync(settings, { preview = false, run = streamCommand, dir
const incoming = `${progressFile}.incoming-${process.pid}`;
fs.writeFileSync(incoming, JSON.stringify(progress,null,2)+'\n'); fs.renameSync(incoming,progressFile);
};
const execute = async (tool, args) => {
const execute = async (tool, args, extra = {}) => {
const started = Date.now();
const timer = setInterval(() => log(`[mirror] Still running this stage (${Math.floor((Date.now() - started) / 1000)} seconds elapsed)`), heartbeatMs);
try {
const output = await run(tool, args, undefined, { env: { ...process.env, ...environment } });
const output = await run(tool, args, undefined, { env: { ...process.env, ...environment }, ...extra });
if (output) log(output.trimEnd());
} finally { clearInterval(timer); }
};
@@ -64,6 +70,11 @@ export async function sync(settings, { preview = false, run = streamCommand, dir
try {
log(`[mirror] ${preview ? 'Checking' : 'Starting sync'} from ${forges[settings.source].name} to ${settings.mirrors.map(m => forges[m].name).join(', ')}`);
saveProgress();
const logDirectory = path.dirname(environment.WEKAN_MIRROR_LOG_FILE || process.env.WEKAN_MIRROR_LOG_FILE || path.join(directory, '.tools/log/mirror', 'mirror-log.txt'));
fs.mkdirSync(logDirectory, {recursive:true});
log('[mirror] Stage 1/3: synchronizing Git repositories to every active target');
for (const target of settings.mirrors) await execute(process.execPath, [engine, '--source', settings.source, '--target', target, '--git-only', ...(preview ? [] : ['--apply'])], {mirrorLog:path.join(logDirectory, `${target}.txt`)});
log('[mirror] Stage 2/3: archiving source content locally');
log(`[mirror] Reading source issues, pull requests, comments and releases`);
if (resume && progress.sourceReady && snapshot === durableSnapshot) log('[mirror] Resuming saved source snapshot; completed collection is reused');
else if (!sourceSnapshotFile) await execute(process.execPath, [engine, '--source', settings.source, '--export-source', snapshot, ...(settings.source === 'github' ? ['--incremental', ...(preview ? ['--cache-only'] : [])] : [])]);
@@ -71,19 +82,35 @@ export async function sync(settings, { preview = false, run = streamCommand, dir
log('[mirror] Updating local archive and static pages');
try { if (!progress.archiveDone) await execute(process.execPath, [engine, '--source', settings.source, '--archive-only', ...(preview ? [] : ['--apply']), '--snapshot', snapshot]); progress.archiveDone = true; saveProgress(); }
catch (error) { failed = true; log(`[archive] failed: ${error.message}`); }
for (const target of settings.mirrors) {
if (progress.targetsDone.includes(target)) { log(`[mirror] ${forges[target].name}: already completed; skipping`); continue; }
log(`[mirror] ${preview ? 'Checking' : 'Syncing'} ${forges[target].name}`);
if (failed) { log('Local archive failed; destination content synchronization was not started'); return false; }
const manifest = fs.existsSync(snapshot) ? JSON.parse(fs.readFileSync(snapshot, 'utf8')) : {};
const totals = {issues:(manifest.issueFiles || manifest.issues || []).length, releases:(manifest.releaseFiles || manifest.releases || []).length};
const counters = new Map(settings.mirrors.map(name => [name, {started:'waiting', issues:0, releases:0, status:'waiting', partial:''}]));
const dashboard = () => log('[mirror progress] ' + [...counters].map(([name,c]) => `${name}: started ${c.started}; issues ${c.issues}/${totals.issues}; releases ${c.releases}/${totals.releases}; ${c.status}`).join(' | '));
dashboard();
log('[mirror] Stage 3/3: synchronizing local content to all targets in parallel');
await Promise.all(settings.mirrors.map(async target => {
if (progress.targetsDone.includes(target)) { const c=counters.get(target); c.status='previously completed'; c.issues=totals.issues; c.releases=totals.releases; dashboard(); log(`[mirror] ${forges[target].name}: already completed; skipping`); return; }
const mirrorLog = path.join(logDirectory, `${target}.txt`);
const started = new Date().toISOString();
const counter = counters.get(target); counter.started = started; counter.status = 'running'; dashboard();
fs.appendFileSync(mirrorLog, `Started ${started}\n`);
log(`[${target}] Started ${started}; log: ${mirrorLog}`);
try {
const args = [...(preview ? ['--preview'] : []), '--source', settings.source, '--snapshot', snapshot, '--skip-archive'];
if (platform === 'win32') {
// Native batches require cmd.exe, which parses paths as commands.
// Run their existing engine with the same flags as separate argv.
await execute(process.execPath, [engine, '--target', target, '--code', ...(preview ? [] : ['--apply']), ...args.filter(arg => arg !== '--preview')]);
} else await execute('bash', [path.join(directory, `releases/mirror-${target}.sh`), ...args]);
await execute(process.execPath, [engine, '--target', target, ...(preview ? [] : ['--apply']), '--source', settings.source, '--snapshot', snapshot, '--skip-archive'], {mirrorLog, onOutput(chunk) {
counter.partial += chunk;
const lines = counter.partial.split('\n'); counter.partial = lines.pop();
for (const line of lines) {
if (new RegExp(`^\\[${target}\\] processed: issue `).test(line)) counter.issues++;
if (new RegExp(`^\\[${target}\\] processed: release `).test(line)) counter.releases++;
}
dashboard();
}});
counter.status = 'finished'; dashboard();
progress.targetsDone.push(target); saveProgress();
} catch (error) { failed = true; log(`[${target}] failed: ${error.message}`); }
}
log(`[${target}] Finished successfully; log: ${mirrorLog}`);
} catch (error) { counter.status = 'failed'; dashboard(); failed = true; log(`[${target}] failed: ${error.message}; log: ${mirrorLog}`); fs.appendFileSync(mirrorLog, `Failed: ${error.message}\n`); }
}));
} finally { fs.rmSync(work, { recursive: true, force: true }); }
progress.complete = !failed; saveProgress();
log(`[mirror] ${failed ? 'Finished with failures' : 'Finished successfully'}`);
+5 -5
View File
@@ -15,20 +15,20 @@ export function createLimiter({stateFile=path.join(root,'.tools/mirror/rate-limi
const queues=new Map();
const load=()=>{if(!fs.existsSync(stateFile))return {};const state=JSON.parse(fs.readFileSync(stateFile,'utf8'));if(state.version!==1 || !state.hosts || Object.values(state.hosts).some(n=>!Number.isFinite(n)))throw Error('Invalid forge rate-limit state');return state.hosts;};
const save=(host,until)=>{const hosts=load();hosts[host]=Math.max(hosts[host]||0,until);fs.mkdirSync(path.dirname(stateFile),{recursive:true});const file=stateFile+`.incoming-${process.pid}`;fs.writeFileSync(file,JSON.stringify({version:1,hosts})+'\n');fs.renameSync(file,stateFile);};
async function wait(host){let ms=(load()[host]||0)-now();if(ms>0)log(`[${host}] rate limit: waiting ${Math.ceil(ms/1000)} seconds`);while(ms>0){await pause(Math.min(ms,60000));ms=(load()[host]||0)-now();}}
async function perform(host,operation){
async function wait(host,{maxWaitMs=Infinity}={}){let ms=(load()[host]||0)-now();if(ms>maxWaitMs)throw Error(`${host}: cooldown deferred; retry on next run`);if(ms>0)log(`[${host}] rate limit: waiting ${Math.ceil(ms/1000)} seconds`);while(ms>0){await pause(Math.min(ms,60000));ms=(load()[host]||0)-now();}}
async function perform(host,operation,options={}){
for(let attempt=0;attempt<6;attempt++){
await wait(host);const result=await operation();const response=result.response || result;
await wait(host,options);const result=await operation();const response=result.response || result;
const headers=response.headers || new Headers();
const remaining=headers.get('x-ratelimit-remaining') ?? headers.get('ratelimit-remaining');
const reset=Number(headers.get('x-ratelimit-reset') ?? headers.get('ratelimit-reset'))*1000;
if(remaining==='0'&&reset>now())save(host,reset+1000);
const limited=response.status===429 || ((response.status===403 || response.status===503) && (headers.has('retry-after') || remaining==='0' || /rate limit|too many requests|abuse detection/i.test(result.error || ''))) || (response.status>=400 && /rate limit|too many requests|abuse detection/i.test(result.error || ''));
if(limited){await response.body?.cancel?.();save(host,now()+cooldown(headers,now(),attempt));if(attempt===5)throw Error(`${host}: rate limit persisted after six attempts; saved cooldown will be respected next run`);continue;}
if(limited){await response.body?.cancel?.();save(host,now()+cooldown(headers,now(),attempt));if(options.noRetry)throw Error(`${host}: rate-limited attachment deferred; retry on next run`);if(attempt===5)throw Error(`${host}: rate limit persisted after six attempts; saved cooldown will be respected next run`);continue;}
save(host,now()+spacing);return result;
}
}
return {wait,perform:(host,fn)=>{const result=(queues.get(host)||Promise.resolve()).then(()=>perform(host,fn));queues.set(host,result.then(()=>{},()=>{}));return result;}};
return {wait,perform:(host,fn,options)=>{const result=(queues.get(host)||Promise.resolve()).then(()=>perform(host,fn,options));queues.set(host,result.then(()=>{},()=>{}));return result;}};
}
export const limiter=createLimiter();
export async function limitedFetch(url,options,fetcher=fetch){return limiter.perform(new URL(url).hostname,()=>fetcher(url,{...options,signal:AbortSignal.timeout(options?.timeoutMs || 3600000)}));}