mirror of
https://github.com/wekan/wekan.git
synced 2026-11-08 11:25:19 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
634e32620c | ||
|
|
f40d29a6e5 | ||
|
|
cc00713b2c | ||
|
|
b232abe1a0 | ||
|
|
2a22a42a96 | ||
|
|
a6da38e763 | ||
|
|
f23a18515a | ||
|
|
ffde8e84ee | ||
|
|
39f4f573d8 | ||
|
|
f665f7cdaf | ||
|
|
ec5a6464fc | ||
|
|
3ad3491f39 | ||
|
|
4c788beef0 | ||
|
|
84514bb203 | ||
|
|
f29c14def7 | ||
|
|
0443b18e38 | ||
|
|
670c203113 | ||
|
|
fb74726c9e | ||
|
|
fda197bb00 | ||
|
|
74ecca7814 | ||
|
|
a076987c96 | ||
|
|
e35ba91dfd | ||
|
|
dad65fb6c5 | ||
|
|
c3ce54fa21 | ||
|
|
220a6beb69 | ||
|
|
ea16ef95e4 | ||
|
|
1ee15661a3 | ||
|
|
9910a2c138 | ||
|
|
de9da747a7 | ||
|
|
e34b55d698 | ||
|
|
251e352245 | ||
|
|
629577951a | ||
|
|
f7a816eb22 | ||
|
|
641062f8cd | ||
|
|
bceb89d928 | ||
|
|
888e0e6bcd | ||
|
|
a5e111f910 | ||
|
|
30abbc9ae2 | ||
|
|
d2780f0082 | ||
|
|
1ca33bf70f | ||
|
|
01fa30d1c3 | ||
|
|
cfeeb65d46 | ||
|
|
b71db72ab0 | ||
|
|
196b82d78c | ||
|
|
9683042c4e | ||
|
|
86dc493da9 | ||
|
|
44e3e1fd75 | ||
|
|
f753dedd93 | ||
|
|
4e3802901e | ||
|
|
9e8d8da268 | ||
|
|
103e4dface | ||
|
|
587f4ec335 | ||
|
|
cfc2480e54 | ||
|
|
927faa7215 | ||
|
|
f51c7439f9 | ||
|
|
b5ddb610a8 | ||
|
|
8fbc620750 | ||
|
|
503ada2fbe | ||
|
|
2c5cdd78a9 | ||
|
|
5b2ce8f6f3 | ||
|
|
c6c3b90bef | ||
|
|
798e86ec68 | ||
|
|
1adfb3bec3 | ||
|
|
9d307d8518 | ||
|
|
d2d90abf53 | ||
|
|
414e846517 | ||
|
|
c78c7f3c52 | ||
|
|
e410234e78 | ||
|
|
92b911aa94 | ||
|
|
fe022ff608 | ||
|
|
44ff8c566b | ||
|
|
cb16fd4d30 | ||
|
|
87b9e45fb1 | ||
|
|
9530e99ea9 | ||
|
|
29f1980b24 | ||
|
|
7f47bbf7f3 | ||
|
|
dda51a1e5c | ||
|
|
0b154538d3 | ||
|
|
f7def463be | ||
|
|
6e9c26d53b | ||
|
|
6c534f40f6 | ||
|
|
55a8bb26ca | ||
|
|
64a60f1897 | ||
|
|
1709b89e7b | ||
|
|
885d65b3b8 | ||
|
|
d07cc31d21 | ||
|
|
c45e99b02b | ||
|
|
88fa9f5461 | ||
|
|
ac0f8696fa | ||
|
|
d3afacd15f | ||
|
|
98c90fd8a6 | ||
|
|
3fb32bc4b5 | ||
|
|
47fb0f2771 | ||
|
|
51d025e131 | ||
|
|
e5e09f52c8 | ||
|
|
31786d7574 | ||
|
|
708478423a | ||
|
|
00c917ab39 | ||
|
|
4b926a60fa | ||
|
|
829fb2ae9d | ||
|
|
e8766c17b9 | ||
|
|
81ed6d9899 | ||
|
|
8db16132cb | ||
|
|
a187b70013 | ||
|
|
ff77543b5c | ||
|
|
72abc938dd | ||
|
|
3b524f0712 | ||
|
|
fd170dfc32 | ||
|
|
2f141de75b | ||
|
|
4b82dd70fb | ||
|
|
ccfceb7b77 | ||
|
|
263da8892d | ||
|
|
f22aeb7bcd | ||
|
|
7672a79147 | ||
|
|
cf1e89bb9e | ||
|
|
93ea560109 | ||
|
|
2cb3f6b13f | ||
|
|
f8bb9f02f7 | ||
|
|
54fdfd33ca | ||
|
|
6100709231 | ||
|
|
edea4b20e1 | ||
|
|
0a43570fc8 | ||
|
|
e74cc64642 | ||
|
|
115901a7c8 | ||
|
|
4f0293f5cd | ||
|
|
91c36f1cea | ||
|
|
7f7db419c7 | ||
|
|
b6403e798e | ||
|
|
316232f694 | ||
|
|
0233b49593 | ||
|
|
96367d2134 | ||
|
|
c8adbff586 | ||
|
|
4c585a2b81 | ||
|
|
646ab17f46 | ||
|
|
68277a708d | ||
|
|
e55898711f | ||
|
|
f1add7ab2b | ||
|
|
36b0109fc3 | ||
|
|
b82f539a41 | ||
|
|
2998b14bc2 | ||
|
|
0d2e977d90 | ||
|
|
0b8f090343 | ||
|
|
9b6b1ffe6f | ||
|
|
11ac7f8a97 | ||
|
|
5d94f1b92b | ||
|
|
e520065604 | ||
|
|
41fb26dff6 | ||
|
|
c91ef98426 | ||
|
|
05217bbb9e | ||
|
|
d8fd82d57d | ||
|
|
f523001c3e | ||
|
|
ccafc4db96 | ||
|
|
9ba7cfcb80 | ||
|
|
545e1bc9fb | ||
|
|
5a398d5acc | ||
|
|
44a6f7e74a | ||
|
|
07756eecd0 | ||
|
|
ba489652ec | ||
|
|
036cbff311 | ||
|
|
aa0ae67f83 | ||
|
|
460df4ada0 | ||
|
|
25ed0a5ccb |
No files matched your search
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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  / .
|
||||
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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
Generated
+2
-2
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wekan",
|
||||
"version": "v11.77.0",
|
||||
"version": "v11.78.0",
|
||||
"description": "Open-Source kanban",
|
||||
"private": true,
|
||||
"repository": {
|
||||
|
||||
@@ -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,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="<p>The REST API allows you to control and extend Wekan with ease.</p>
|
||||
" 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="<p>The REST API allows you to control and extend Wekan with ease.</p>
|
||||
<p>If you are an end-user and not a dev or a tester, <a href="https://github.com/wekan/wekan/issues/new">create an issue</a> to request new APIs.</p>
|
||||
<blockquote>
|
||||
<p>All API calls in the documentation are made using <code>curl</code>. However, you are free to use Java / Python / PHP / Golang / Ruby / Swift / Objective-C / Rust / Scala / C# or any other programming languages.</p>
|
||||
@@ -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,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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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/);
|
||||
}
|
||||
@@ -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/);
|
||||
});
|
||||
@@ -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/);
|
||||
});
|
||||
@@ -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/);
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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\(\)/);
|
||||
});
|
||||
}
|
||||
@@ -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/);
|
||||
});
|
||||
@@ -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; });
|
||||
@@ -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});}
|
||||
});
|
||||
|
||||
@@ -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});}
|
||||
});
|
||||
@@ -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')));
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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
@@ -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)}×tamp=${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
@@ -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'}`);
|
||||
|
||||
@@ -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)}));}
|
||||
|
||||
Reference in new issue
Block a user