Compare commits

..

373 Commits

Author SHA1 Message Date
Martin Gruner
eb0601da6d Maintenance: update files for 6.5.3 2026-03-10 14:37:11 +01:00
Martin Gruner
abc1f6154c Maintenance: Improve redis logging. 2026-03-05 14:10:20 +01:00
Rolf Schmidt
58619074c3 Maintenance: Fix naming for block operators.
Co-authored-by: Dominik Klein <dk@zammad.com>
Co-authored-by: Florian Liebe <fl@zammad.com>
2026-03-05 12:27:36 +01:00
Florian Liebe
081f19205c Maintenance: Improve ticket_related endpoint.
Co-authored-by: Dominik Klein <dk@zammad.com>
2026-03-05 12:27:35 +01:00
Florian Liebe
5bb072c53f Maintenance: Improve import related endpoints.
Co-authored-by: Dominik Klein <dk@zammad.com>
2026-03-05 12:27:34 +01:00
Florian Liebe
18178c6196 Maintenance: Improve ticket update handling using REST API/GraphQL mutation.
Co-authored-by: Dominik Klein <dk@zammad.com>
2026-03-05 12:27:06 +01:00
Martin Gruner
b0db09aab4 Trigger build for packager.io 2026-03-04 08:31:23 +01:00
Klaus Mueller
5be5626a10 Maintenance: Update publiccode.yml to add used by information. 2026-03-02 09:18:11 +01:00
Florian Liebe
c7da54926d Maintenance: Update brakeman ignore file. 2026-03-02 07:40:23 +01:00
Florian Liebe
1395ca8fbe Maintenance: Update various gems. 2026-02-24 07:30:55 +01:00
Mantas Masalskis
a67cb65a71 Maintenance: Clarify Macro administrator permission 2026-02-21 20:05:23 +02:00
Dusan Vuckovic
160714e22d Fixes #5948 - Default time diff is applying wrong units when the associated fields are missing. 2026-02-19 15:04:36 +00:00
Florian Liebe
b0c653accd Maintenance: Update gem dependencies. 2026-02-12 10:13:54 +01:00
Weblate
55d97c3d23 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Alfredo Sola <alfredo@sola.es>
Co-authored-by: Rok Poje <rok.poje@mintpos.si>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chrand818 <can@telenabler.com>
Co-authored-by: kands-software <admin@kands.pl>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/es/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2026-02-09 18:08:55 +00:00
Zammad Weblate
f6c9f57f59 Translations update from Zammad-Translations 2026-02-09 12:38:02 +01:00
Dusan Vuckovic
9faef42efa Fixes #5935 - Leading spaces get deleted when inserting mentions in Firefox. 2026-02-06 14:41:29 +00:00
Dusan Vuckovic
6924af8e11 Fixes #5942 - If a larger list of groups is available in the ticket, it is not possible to scroll the groups. 2026-02-04 16:17:45 +00:00
Weblate
90191cfc80 Maintenance: Update translated files and catalog 2026-02-04 06:38:32 +00:00
Weblate
6b658cc55b Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Mixer <mixer@mixercz.cz>
Co-authored-by: Sumonchai Wongphithak <sumonchai@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/cs/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/th/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2026-02-03 22:00:37 +00:00
Mantas Masalskis
e27cc96d5b Fixes #5931 - Knowledge Base Editor freezes when adding video 2026-02-03 15:39:02 +02:00
Florian Liebe
d278bdb09e Maintenance: Update brakeman ignore file.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2026-01-30 11:07:54 +01:00
Dusan Vuckovic
12b6c85675 Fixes #5916 - Blue text after mentioning. 2026-01-29 13:18:57 +00:00
Rolf Schmidt
5ca61ee542 Maintenance: Add rake task to get cache io performance.
Co-authored-by: Florian Liebe <fl@zammad.com>
2026-01-28 12:37:06 +01:00
Mantas Masalskis
f21c824bb9 Fixes #5923 - KB agent search inside a category broken in SQL fallback mode 2026-01-28 12:10:24 +02:00
Weblate
e7b53f6ffe Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: Karoly Nagy <cpt.charlesg@gmail.com>
Co-authored-by: Mixer <mixer@mixercz.cz>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/cs/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/en_GB/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translation: Zammad/Zammad (stable version)
2026-01-26 09:02:00 +00:00
Zammad Weblate
5157babf3c Translations update from Zammad-Translations 2026-01-23 07:12:51 +01:00
Mantas Masalskis
7f7854c4ce Fixes #5914 - Vertical bar pipe in macro name is interpreted as bold 2026-01-22 17:07:57 +02:00
Martin Gruner
43c2242a28 Revert "Maintenance: Use CI_JOB_TOKEN for automated commit creation" 2026-01-21 13:47:30 +01:00
Martin Gruner
e4f7a0d026 Maintenance: Use CI_JOB_TOKEN for automated commit creation 2026-01-21 11:56:10 +01:00
Dusan Vuckovic
c51d48338d Maintenance: Improve error handling in the chat configuration page. 2026-01-20 10:16:58 +00:00
Martin Gruner
67f668c1aa Maintenance: Update contacts for publiccode.yml 2026-01-19 12:42:26 +01:00
Florian Liebe
fdeb07bd82 Maintenance: Update release script to update publiccode.yml. 2026-01-19 10:32:11 +01:00
Zammad Weblate
84d388bccd Translations update from Zammad-Translations 2026-01-19 07:59:03 +01:00
Florian Liebe
581a055bfa Maintenance: Add publiccode.yml file.
Co-authored-by: Martin Gruner <mg@zammad.com>
2026-01-16 14:04:43 +01:00
Weblate
36bc8990e0 Maintenance: Update translated files and catalog 2026-01-13 07:58:29 +00:00
Weblate
e36b406462 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Andre Oli <n0bledeath.117@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_PT/
Translation: Zammad/Zammad (stable version)
2026-01-13 07:54:14 +00:00
Weblate
280032b487 Maintenance: Update translated files and catalog 2026-01-12 12:03:04 +00:00
Weblate
68a9934225 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: Andre Oli <n0bledeath.117@gmail.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Karoly Nagy <cpt.charlesg@gmail.com>
Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/en_GB/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_PT/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2026-01-12 12:00:37 +00:00
Florian Liebe
ec36ac7f3c Maintenance: Update httparty gem. 2026-01-12 10:25:51 +01:00
Florian Liebe
c55d2bcb43 Maintenance: Renew client configuration for Keycloak. 2026-01-08 15:29:09 +01:00
Mantas Masalskis
5ee205d566 Maintenance: Update copyright information. 2026-01-02 13:45:18 +02:00
Marcel Bialas
f701ab196e Fixes #5884 - Preserve image dimensions when forwarding 2025-12-30 09:47:44 +01:00
Weblate
014b1fa1d1 Maintenance: Update translated files and catalog 2025-12-23 05:36:00 +00:00
Weblate
ba6ff57471 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: Karoly Nagy <cpt.charlesg@gmail.com>
Co-authored-by: Mantas Masalskis <mantas@idev.lt>
Co-authored-by: Mixer <mixer@mixercz.cz>
Co-authored-by: Murat Özalp <cunyor@gmail.com>
Co-authored-by: PackyZhang <zhanghongbang@live.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/cs/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/en_GB/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/lt/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hans/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-12-23 05:32:20 +00:00
Mantas
ec5265c1e7 Maintenance: Update aws-sdk-s3 to 1.208.0 (CVE-2025-14762) 2025-12-19 20:09:34 +02:00
Mantas Masalskis
889983bcf0 Fixes #5893 - Possible null error in ticket state update when checklist enabled 2025-12-19 20:01:03 +02:00
Mantas Masalskis
5697ef96d5 Fixes #5887 - Knowledge Base search extremely slow despite fast Elasticsearch response 2025-12-16 18:50:51 +02:00
Martin Gruner
3f6d7e5749 Maintenance: Improve submit for language data to supabase 2025-12-12 07:40:00 +01:00
Martin Gruner
a7d8c86c90 Maintenance: repair SLES 15 build 2025-12-08 11:41:46 +01:00
Weblate
1e4f6fe8c4 Maintenance: Update translated files and catalog 2025-12-08 06:44:20 +00:00
Weblate
84f023a1c3 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: albanobattistella <albanobattistella@gmail.com>
Co-authored-by: jeysunit <jeysunit@bcamp.net>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ja/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-12-08 06:41:46 +00:00
Rolf Schmidt
2b72607ee6 Fixes #5882 - S/MIME with Cc but no To field fails on decryption. 2025-12-05 16:57:56 +01:00
Weblate
63ed9c6515 Maintenance: Update translated files and catalog 2025-12-03 12:43:41 +00:00
Weblate
f0c09ba31c Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Ivan Perovic <ip@zammad.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chrand818 <can@telenabler.com>
Co-authored-by: jeysunit <jeysunit@bcamp.net>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ja/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-12-03 12:41:22 +00:00
Dusan Vuckovic
f5ff6c7df1 Maintenance: Stabilize calendar spec. 2025-12-03 12:01:41 +00:00
Dominik Klein
3024f4280c Maintenance: Add small OpenID Connect improvements when discovery feature is not used.
Co-authored-by: Florian Liebe <fl@zammad.com>
Co-authored-by: Dominik Klein <dk@zammad.com>
2025-12-03 09:44:59 +01:00
Weblate
bc4a754e13 Maintenance: Update translated files and catalog 2025-11-28 10:57:36 +00:00
Weblate
e99e416142 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: Mantas Masalskis <mantas@idev.lt>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/lt/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-11-28 10:53:53 +00:00
Benjamin Scharf
4f816db6d2 Fixes #5624 - SLA - Persisting Update Time 2025-11-27 09:59:29 +01:00
Rolf Schmidt
b163502a05 Fixes #5846 - wrong umask leads to major issues which are hard to identify.
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Tarek Shehab <tsh@zammad.com>
2025-11-27 09:23:50 +01:00
Martin Gruner
8c5e23c02f Fixes #5851 - Admin -> Macros - Translation in French - bad escaping of quote characters 2025-11-26 07:54:59 +01:00
Weblate
4d4a672aa1 Maintenance: Update translated files and catalog 2025-11-24 13:24:07 +00:00
Weblate
c79cca893a Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: JonggwonSeon <jg.seon@sdt.inc>
Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: Mantas Masalskis <mantas@idev.lt>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ko/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/lt/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-11-24 01:00:07 +00:00
Rolf Schmidt
1734c7f919 Fixed #5852 - core workflow: saved value is lost when it is restricted but another field is changed.
Co-authored-by: Dominik Klein <dk@zammad.com>
2025-11-20 12:28:28 +00:00
Weblate
24d5ad6931 Maintenance: Update translated files and catalog 2025-11-19 12:43:59 +00:00
Weblate
ceaf10bd3d Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: Borut Baznik <borut.baznik@jhl.si>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: JonggwonSeon <jg.seon@sdt.inc>
Co-authored-by: Karoly Nagy <cpt.charlesg@gmail.com>
Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: Mantas Masalskis <mantas@idev.lt>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Co-authored-by: sigurbjartur <github@sigurbjartur.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/en_GB/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/is/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ko/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/lt/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-11-19 12:41:51 +00:00
Rolf Schmidt
823dec261d Fixes #5850 - connection test script for systems with firewall/proxy setup.
Co-authored-by: Martin Gruner <mg@zammad.com>
2025-11-19 10:23:31 +01:00
Rolf Schmidt
c8495510c8 Maintenance: Move task manager singleton to own file for better patchability.
Co-authored-by: Dominik Klein <dk@zammad.com>
2025-11-18 14:20:58 +01:00
Martin Gruner
0d37a61f90 Maintenance: Add gitlab CI job to submit stats to supabase 2025-11-17 11:24:33 +01:00
Rolf Schmidt
723a46e61e Maintenance: Fix nil error on missing description in hardware of the system report. 2025-11-13 19:23:47 +01:00
Dominik Klein
60ba952302 Fixes #5843 - Freshdesk migration error when address fields are given as an string instead of an array. 2025-11-11 14:32:24 +01:00
Martin Gruner
1ff2c52d50 Maintenance: add ignore for .envrc
(cherry picked from commit 17caabd26c)

Co-authored-by: Martin Gruner <mg@zammad.com>
2025-11-11 10:10:30 +01:00
Ben
51909d1d94 Fixes #5837 - Inline images exceed container width, causing horizontal scrolling 2025-10-31 14:44:01 +00:00
Dusan Vuckovic
443f65b2ff Fixes #5835 - Zammad logo is showing "no new notifications" for a customer. 2025-10-30 19:25:18 +00:00
Weblate
3fc1bbac46 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/en_GB/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-10-29 22:02:17 +00:00
Benjamin Scharf
c47363c0c9 Fixes #5827 - Time accounting - Activity Types are not translated 2025-10-29 09:42:04 +00:00
Mantas Masalskis
ba8e89badb Fixes #5824 - Show human-readable ApplicationModel errors for agent users 2025-10-29 11:37:15 +02:00
Weblate
277fb0e160 Maintenance: Update translated files and catalog 2025-10-27 14:28:07 +00:00
Weblate
8823e12c89 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: António Lima <aplima@euroemails.com>
Co-authored-by: Emanuel Saramago <dev@emanuelsaramago.com>
Co-authored-by: Karoly Nagy <cpt.charlesg@gmail.com>
Co-authored-by: Krzysztof Jereczek <krzysztof@pro2web.pl>
Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: Mantikor <nhyuga.r@gmail.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Phoenix <phoenix.yu@vesoft.com>
Co-authored-by: Rok Poje <rok.poje@mintpos.si>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/en_GB/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_PT/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hans/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-10-27 14:26:09 +00:00
Ben
8e51440c9c Fixes #5819 - Arabic text in the trigger action (e-mail) scrambles the text (RTL --> LTR) 2025-10-23 07:56:26 +01:00
Benjamin Scharf
23b4cf5032 Fixes #5767 - Unfinished customer article behaves weird after restarting the session
Co-authored-by: Benjamin Scharf <bs@zammad.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-10-20 09:35:57 +02:00
Rolf Schmidt
28ab46c49e Maintenance: Add missing relation for store user.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-10-13 13:35:33 +02:00
Benjamin Scharf
07e959c01c Fixes #5809 - Merging tickets does not update the article_count of the target ticket 2025-10-13 12:37:35 +02:00
Mantas Masalskis
cf89261291 Maintenance: Update Rack to 2.2.20 (CVE-2025-61919, CVE-2025-61780) 2025-10-12 16:24:47 +03:00
Marcel Bialas
de26741ee1 Fixes #5813 - S3_URL fails when AWS secret keys contains characters which will be encoded.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-10-10 14:51:09 +02:00
Weblate
abf058ff0d Maintenance: Update translated files and catalog 2025-10-10 07:01:58 +01:00
Weblate
880bc4f121 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: Benicio <vonfeben@init7.net>
Co-authored-by: Carlos Lindo <carloslindo@gmail.com>
Co-authored-by: Dmytro <ramadan2003@ukr.net>
Co-authored-by: Ferenc Kutas <ferenc@kutasferenc.hu>
Co-authored-by: Mantikor <nhyuga.r@gmail.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: PIXELHIZE <pixelhize@gmail.com>
Co-authored-by: Sumonchai Wongphithak <sumonchai@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: kdh949 <donghyun.kim@chungbuk.ac.kr>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ko/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_PT/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/th/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-10-09 22:00:07 +00:00
Dusan Vuckovic
2955515697 Fixes #5811 - Unassigning organization requires editing the assignment first. 2025-10-09 12:37:10 +01:00
Florian Liebe
aaab9af13a Maintenance: Update rack gem. 2025-10-09 09:35:45 +02:00
Martin Gruner
fcae2a849b Maintenance: update uri gem 2025-10-08 08:10:27 +02:00
Dusan Vuckovic
de85e4a5c4 Fixes #5807 - Broken display in the date picker field. 2025-10-07 14:37:13 +01:00
Mantas Masalskis
5caba850fc Fixes #5806 - KB feed returns error 500 for non-existant category 2025-10-03 18:53:29 +03:00
Rolf Schmidt
d5d8c7ecdf Fixes #5194 - Report allows to pick February 31st.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-10-02 16:25:56 +02:00
Florian Liebe
4fca9878ce Maintenance: Update SAML client in CI. 2025-10-02 12:24:13 +02:00
Weblate
7ccef40e9b Maintenance: Update translated files and catalog 2025-09-29 07:03:01 +01:00
Weblate
7764d3be3f Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Carlos Lindo <carloslindo@gmail.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Ferenc Kutas <ferenc@kutasferenc.hu>
Co-authored-by: Ivan Perovic <ip@zammad.com>
Co-authored-by: Jonas Mandrén <Jonas@mandren.se>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_PT/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-09-27 22:00:08 +00:00
Mantas
cb9e869e8b Maintanance: Update Rack to 2.2.18 (CVE-2025-59830) 2025-09-27 09:19:13 +03:00
Rolf Schmidt
1210d52ccc Fixes #5230 - Wrong tooltip and deletable action in Postmaster-Filter.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-09-25 14:31:26 +02:00
Florian Liebe
b958e71fef Maintenance: Allow packager.io related test jobs to fail. 2025-09-25 07:53:22 +02:00
Rolf Schmidt
b18da133cb Fixes #5785 - Case insensitive in customer mail using the API not always true.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-09-24 10:25:44 +02:00
Florian Liebe
3790f5f191 Follow-up cc3ef143d1750aa72fe5d38781a862a1f7473de1: Maintenance: Improve handling of sensitive data in HTTP log. 2025-09-24 09:20:16 +02:00
Florian Liebe
700e2fa128 Release Zammad 6.5.2. 🚀 2025-09-24 06:33:54 +02:00
Florian Liebe
28947b215e Follow-up cc3ef143: Maintenance: Improve handling of sensitive data in HTTP log.
Co-authored-by: Dominik Klein <dk@zammad.com>
2025-09-23 16:06:28 +02:00
Florian Liebe
a1cd0b3d06 Follow-up cc3ef143d1750aa72fe5d38781a862a1f7473de1: Maintenance: Improve handling of sensitive data in HTTP log.
Co-authored-by: Dominik Klein <dk@zammad.com>
2025-09-23 16:06:28 +02:00
Florian Liebe
1c328d2ede Maintenance: Improve handling of sensitive data in HTTP log.
Co-authored-by: Dominik Klein <dk@zammad.com>
2025-09-23 16:06:28 +02:00
Florian Liebe
b49ff68d86 Maintenance: Fix rubocop issues. 2025-09-23 16:06:28 +02:00
Dominik Klein
3c4bb94781 Maintenance: Add some improvements for the HTTP log handling.
Co-authored-by: Florian Liebe <fl@zammad.com>
Co-authored-by: Mantas Masalskis <mm@zammad.com>
Co-authored-by: Dominik Klein <dk@zammad.com>
2025-09-23 16:06:28 +02:00
Mantas Masalskis
630960a0b6 Maintenance: Improve logging behaviour 2025-09-23 16:06:28 +02:00
Florian Liebe
cb84fa6dca Follow-up 9acec78f: Fixes #5774 - Closing a ticket using a macro skips the "Incomplete Ticket Checklist" notification. 2025-09-23 15:51:05 +02:00
Rolf Schmidt
bdeb284836 Fixes #5792 - Scheduler config will be changed with variable execution value when executed.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-09-23 15:45:59 +02:00
Weblate
a4e079f41e Maintenance: Update translated files and catalog 2025-09-22 07:45:38 +01:00
Weblate
a0a0bc4bb5 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Carlos Lindo <carloslindo@gmail.com>
Co-authored-by: Ivan Perovic <ip@zammad.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_PT/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-09-22 06:42:53 +00:00
Florian Liebe
c0a5361e38 Maintenance: Update rexml gem. 2025-09-19 08:23:21 +02:00
Rolf Schmidt
71ffa8194a Fixes #5782 - Trigger placeholder missing when language is set to german.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-09-17 14:18:19 +02:00
Benjamin Scharf
3b34f3d92d Fixes #5774 - Closing a ticket using a macro skips the "Incomplete Ticket Checklist" notification
Co-authored-by: Benjamin Scharf <bs@zammad.com>
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-09-12 14:08:43 +01:00
Mantas Masalskis
a54273d964 Fixes #5769 - ChecklistTablesWrongColumnType migration fails on MySQL-to-Postgres instances 2025-09-12 11:31:31 +03:00
Weblate
69169c3ab4 Maintenance: Update translated files and catalog 2025-09-10 23:04:09 +01:00
Weblate
9e856fad19 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: Carlos Lindo <carloslindo@gmail.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Ferenc Kutas <ferenc@kutasferenc.hu>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Ralf Schmid <rsc@zammad.com>
Co-authored-by: Rudolfet <hermy.t.m1997@gmail.com>
Co-authored-by: Shadowdenim <mikebeelen111@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: csongoose <csongoose@gmail.com>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Co-authored-by: newsam202 <newsam202@naver.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ko/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_PT/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-09-10 22:00:18 +00:00
Ben
daf1664de5 Fixes #5772 - Translations - Storage methods are not translated on exemples on storage parameters 2025-09-10 06:58:54 +01:00
Rolf Schmidt
e1551c07a1 Maintenance: Move session elements to own function for monkey patching.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-09-08 16:59:45 +02:00
Dusan Vuckovic
ef4d833c04 Maintenance: Stabilize field typing in tests. 2025-09-08 11:46:20 +02:00
Mantas Masalskis
14056d81b6 Fixes #5756 - AI Agent: Result is sometimes not applied to the ticket 2025-09-05 16:42:14 +03:00
Benjamin Scharf
f5adf773b1 Fixes #4927 - Scheduler job refers to time in past in column "Scheduled for" 2025-09-04 07:23:10 +01:00
Florian Liebe
78771b5afa Maintenance: Update Zendesk cassettes. 2025-09-03 12:43:20 +02:00
Dusan Vuckovic
89317a96cb Maintenance: Stabilize ticket edit test. 2025-09-03 10:49:20 +01:00
Dusan Vuckovic
c8b441806c Fixes #5758 - Missing ui_ticket_create_notes setting on existing systems. 2025-09-02 12:33:27 +01:00
Ben
0430e4e6d8 Fixes #5751 - Login -> 2FA - Translation in French - bad escaping of quote characters 2025-08-29 07:22:39 +01:00
Florian Liebe
ccaca6bf8c Maintenance: Update OpenStreetMap related test. 2025-08-28 11:42:45 +02:00
Florian Liebe
6872fbe99b Fixes #5752 - Wrong database column type for checklists + checklist templates after migration to PostgreSQL. 2025-08-27 11:51:42 +02:00
Martin Gruner
a659a77fe2 Maintenance: Unpin Chrome version in CI 2025-08-22 15:23:34 +02:00
Rolf Schmidt
f728a3835d Fixes #5746 - Logout does not work because of nil error (OpenID Connect).
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-08-22 11:33:19 +02:00
Weblate
dad1996338 Maintenance: Update translated files and catalog 2025-08-21 10:27:14 +01:00
Weblate
d836fd8d64 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mathew-uems <mathew.chen@edgentauems.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-08-21 09:25:06 +00:00
Martin Gruner
e20f03a6bb Fixes #5732 - Attachments cannot be uploaded to the knowledge base one after the other 2025-08-20 08:55:00 +02:00
Florian Liebe
89fca58a4e Fixes #5744 - Fetching children of a knowledge base category returns wrong data. 2025-08-20 08:44:03 +02:00
Weblate
5882b1220b Maintenance: Update translated files and catalog 2025-08-18 23:02:11 +01:00
Weblate
c751ce6772 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (stable version)
2025-08-18 22:00:04 +00:00
Florian Liebe
6b60e7b476 Maintenance: Update zammad-auth configuration. 2025-08-18 09:57:08 +02:00
Florian Liebe
ad0b1bd231 Maintenance: Update Rails to version 7.2.2.2. 2025-08-15 13:09:47 +02:00
Florian Liebe
e55bdaf93d Release Zammad 6.5.1. 🚀 2025-08-13 07:47:17 +02:00
Dusan Vuckovic
cce9a3941e Maintenance: Improve HTML output.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-08-12 14:28:53 +02:00
Mantas Masalskis
ba9454278d Maintenance: Improve KB global search 2025-08-12 14:28:53 +02:00
Weblate
a34afda537 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translation: Zammad/Zammad (stable version)
2025-08-07 22:00:04 +00:00
Florian Liebe
39a8ea8a1b Maintenance: Drop form helpers spec. 2025-08-07 13:05:51 +02:00
Florian Liebe
0afb944485 Maintenance: Improve form helpers spec. 2025-08-07 09:15:11 +02:00
Weblate
01ded2ca0c Maintenance: Update translated files and catalog 2025-08-07 06:44:23 +01:00
Weblate
3bdea052f5 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Mauricio Rampazo <mrampazo@outlook.com>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Ralf Schmid <rsc@zammad.com>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Szkolarz <lukasz.szkolarz@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translation: Zammad/Zammad (stable version)
2025-08-07 05:41:59 +00:00
Florian Liebe
d6c15a31e4 Maintenance: Update ruby-saml gem. 2025-08-04 07:36:59 +02:00
Dominik Klein
6fdcf0ce8a Fixes #5663 - OpenID Integration UID field does not work. 2025-08-01 15:46:42 +02:00
Rolf Schmidt
4fd737c20a Fixes #5719 - Search for organizations shows clone icon.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-07-31 14:13:51 +02:00
Benjamin Scharf
edd613feda Maintenance: Desktop view - Warn users that stumble upon the desktop view
Co-authored-by: Benjamin Scharf <bs@zammad.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
2025-07-29 15:10:53 +02:00
Rolf Schmidt
be42afa177 Maintenance: Add table id to report preview table.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-07-29 13:46:34 +02:00
Weblate
68954425fe Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Fernando s Maciel <tecnobeach.itanhaem@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translation: Zammad/Zammad (stable version)
2025-07-26 19:00:02 +00:00
Weblate
cc7247b28b Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Benjamin Scharf <bs@zammad.com>
Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: fwmats <matsunaga@fairway-corp.co.jp>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/es/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ja/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translation: Zammad/Zammad (stable version)
2025-07-23 17:59:02 +00:00
Florian Liebe
1895993661 Maintenance: Update nokogiri and thor gems. 2025-07-23 08:26:11 +02:00
Rolf Schmidt
29af61ae26 Maintenance: Split up article_create to make time accountings and attachments more patchable.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-07-22 10:59:39 +02:00
Weblate
1618eb1423 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: JackYeah <jack11070@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-07-21 06:20:33 +00:00
Weblate
dace359f94 Maintenance: Update translated files and catalog 2025-07-17 23:02:19 +01:00
Weblate
5d7fd26976 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: Martin Bonde <martin@maboni.dk>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Ruben <ruben@bibsyst.no>
Co-authored-by: Sebastian Diaz <sebdiazf@gmail.com>
Co-authored-by: Suat VARLIKLI <suatvarlikli89@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chrand818 <can@telenabler.com>
Co-authored-by: sigurbjartur <github@sigurbjartur.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/es_MX/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/is/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nb_NO/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (stable version)
2025-07-17 22:00:04 +00:00
Moritz Klasen
3e56b2a446 Fixes #5451 - Shortcut "shift + c" not closing ticket when previously set to waiting state 2025-07-17 10:29:13 +02:00
Rolf Schmidt
6878f0d22a Fixes #5713 - Data privacy user deletion foreign key error.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-07-16 13:43:01 +01:00
Florian Liebe
3d2de40aaa Maintenance: Update Zendesk cassettes. 2025-07-10 12:31:53 +02:00
Mantas Masalskis
40e1de022c Fixes #5686 - Tickets that are displayed as a preview in the reporting... 2025-07-09 17:06:52 +03:00
Florian Liebe
916f756e59 Fixes #5668 - Content of an custom ticket object attribute set via trigger variable are not saved, if one of the used variable has an empty content.
Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-07-09 13:47:00 +02:00
Dusan Vuckovic
2f46ec7bd2 Fixes #5699 - nginx restart message contains apache hint. 2025-07-09 12:42:40 +01:00
Dusan Vuckovic
18e2dd5690 Fixes #5691 - When executing a macro via overview, the overview is re-rendered completely, causing the agent to lose its focus in overview. 2025-07-09 12:17:42 +01:00
Benjamin Scharf
34a6168a9d Fixes #5673 - When clicking on reply and clicking on forward of an Article... 2025-07-09 10:42:10 +02:00
Benjamin Scharf
036a893056 Fixes #5696: Overviews - On deletion of the 51th element it will show no entries 2025-07-09 07:56:24 +02:00
Rolf Schmidt
9eb758e473 Maintenance: Stabilize test spec/system/ticket/create_spec.rb:263.
Co-authored-by: Martin Gruner <mg@zammad.com>
2025-07-07 15:20:47 +02:00
Rolf Schmidt
c5c12defea Maintenance: Stabilize ticket create test. 2025-07-07 15:20:47 +02:00
Florian Liebe
53e015428b Fixes #5689 - Pressing the Enter key when editing a saved checklist template deletes checklist items. 2025-07-07 14:37:49 +02:00
Weblate
c85c50b7d4 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translation: Zammad/Zammad (stable version)
2025-07-03 06:41:29 +00:00
Weblate
c5d682a5e7 Maintenance: Update translated files and catalog 2025-07-03 06:54:53 +01:00
Weblate
00018ddfbc Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: Anton Lukianchuk <jdoomm@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (stable version)
2025-07-02 18:07:15 +00:00
Mantas Masalskis
164fced717 Fixes #5690 - After deleting a Trigger, it stays till reloading 2025-07-02 18:28:36 +03:00
Mantas Masalskis
d4f9c417a6 Fixes #5661 - Article Sender Trigger Condition does not Show Values 2025-07-02 14:29:59 +03:00
Weblate
edbc9f8867 Maintenance: Update translated files and catalog 2025-07-02 10:40:50 +01:00
Weblate
42444ccd6d Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anton Lukianchuk <jdoomm@gmail.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: engano <enoceda@puntotec.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/es/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (stable version)
2025-07-02 09:37:21 +00:00
Rolf Schmidt
f7c6e7c336 Fixes #5687 - ticket search parameter with_total_count only works with full=true.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-07-02 10:37:31 +02:00
Dusan Vuckovic
d47e22c95a Maintenance: Improve check for an existing system configuration in services. 2025-07-01 14:38:48 +01:00
Martin Gruner
c7b49f455e Maintenance: switch ES latest tests to ES 8 to avoid test failures 2025-06-30 14:13:17 +02:00
Rolf Schmidt
836dca3a17 Follow up 9e8d0bcbbe - Fixes #5589 - Try other solution via css.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>


(cherry picked from commit ace2dab31f)

2d41dc84 Follow up 9e8d0bcbbe - Fixes #5589 - Try other solution via css.
392fce49 use wait
3cc62d57 tidy
f5bd5349 add debug
2ca971f3 test
71e6dd4d test one
4522fcbf fix test
619c4676 add comment
a098c84b bring debug back
47c428fe test
e8e68690 test
688eab70 sd# Bitte geben Sie eine Commit-Beschreibung für Ihre Änderungen ein. Zeilen,
1ece4630 asdf
5ddf560c try other test style

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-06-27 18:03:44 +02:00
Rolf Schmidt
1fba4606de Fixes #5589 - Display error of copied text in dark mode.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>


(cherry picked from commit 21117b9df5)

21117b9d Fixes #5589 - Display error of copied text in dark mode.

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-06-27 18:03:28 +02:00
Rolf Schmidt
6d3adfd9d5 Revert "Fixes #5589 - Display error of copied text in dark mode." 2025-06-27 11:29:27 +02:00
Rolf Schmidt
21117b9df5 Fixes #5589 - Display error of copied text in dark mode.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-06-27 11:24:26 +02:00
Mantas Masalskis
e349b2df9c Fixes #5634 - Malformed quote after the usage of the ticket article reply will full quote 2025-06-25 15:12:30 +03:00
Weblate
bf43fc3113 Maintenance: Update translated files and catalog 2025-06-23 21:02:46 +01:00
Weblate
3e8e122084 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Elias Grinias <elgrinias@gmail.com>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/el/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translation: Zammad/Zammad (stable version)
2025-06-23 20:00:06 +00:00
Rolf Schmidt
f88d427904 Fixes #5667 - Ticket field named order leads to the issues when indexing in ES.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-06-23 10:49:05 +02:00
Martin Gruner
9a4644ec73 Maintenance: Simplify text modules test to make it pass on Firefox 139+
(cherry picked from commit cb92edf909)

8908b18e Maintenance: Simplify text modules test to make it pass on Firefox 139+
46d287c9 seems to work on both FF 137 and 139

Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-06-18 14:07:19 +02:00
Rolf Schmidt
2791cf8db3 Fixes #5659 - Tickets are not found via the search using the complete ticket hook. 2025-06-18 09:05:01 +02:00
Mantas Masalskis
05c943c2fc Fixes #5660 - Tags that are set via a scheduler are not found via the Zammad search 2025-06-16 16:38:14 +03:00
Weblate
dd6ae28787 Maintenance: Update translated files and catalog 2025-06-16 07:05:54 +01:00
Weblate
924302b58d Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Mantikor <nhyuga.r@gmail.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (stable version)
2025-06-16 06:02:04 +00:00
Mantas Masalskis
06d0d09cb2 Fixes #5623 - Tickets which matches the report profile condition are not shown in the report itself 2025-06-12 20:01:38 +03:00
Weblate
4510102777 Maintenance: Update translated files and catalog 2025-06-11 06:44:12 +01:00
Weblate
4342101ea2 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Casper Andersen <ndersorn@outlook.dk>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Leonerd <3ickleonerd@gmail.com>
Co-authored-by: LomaxThomas <thomas.hartvig@lomax.dk>
Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Rok Poje <rok.poje@mintpos.si>
Co-authored-by: SUMONCHAI WONGPITAK <sumonchai@gmail.com>
Co-authored-by: Sumonchai Wongphithak <sumonchai@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chrand818 <can@telenabler.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fa/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/th/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translation: Zammad/Zammad (stable version)
2025-06-09 22:00:05 +00:00
Mantas Masalskis
931d25862e Fixes #5658 - Webhook returns no such method for time_unit. 2025-06-09 17:23:52 +03:00
Dusan Vuckovic
d4284afad1 Fixes #5646 - Password reset with mandatory 2FA results in a stuck recovery codes dialog. 2025-06-05 10:26:39 +01:00
Florian Liebe
85504067c3 Fixes #5632 - During object attribute deletion only relation inside conditions (e.g. overviews) are checked.
Co-authored-by: Tobias Schäfer <ts@zammad.com>
2025-06-03 14:58:01 +02:00
Florian Liebe
eaa0fd84df Fixes #5644 - 404 on setting default 2FA Device.
Co-authored-by: Tobias Schäfer <ts@zammad.com>
2025-06-03 13:37:59 +02:00
Florian Liebe
9787c9b737 Follow-up c2c4eb3 - Fixes #5609 - Renaming tags is not working.
Co-authored-by: Tobias Schäfer <ts@zammad.com>
2025-06-03 09:28:11 +02:00
Mantas Masalskis
9c7e10a349 Fixes #5642 - When creating a ticket article via API, wrong ticket permission is checked 2025-05-30 17:05:49 +03:00
Dusan Vuckovic
e9d43b3834 Fixes #5639 - Mobile HTML Email Content Horizontal Scrolling Issue - Content Cut Off. 2025-05-30 11:18:46 +01:00
Mantas Masalskis
565ce4d50a Fixes #5475 - User interface looks broken if user has no roles or permissions 2025-05-30 10:06:52 +03:00
Mantas Masalskis
5bfc5b0520 Fixes #5641 - Switched-to-user header visible after logging out 2025-05-29 11:13:17 +03:00
Weblate
6a9655d095 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: SUMONCHAI WONGPITAK <sumonchai@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: horizonxh <horizonxh@163.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/th/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hans/
Translation: Zammad/Zammad (stable version)
2025-05-28 05:13:31 +00:00
Mantas Masalskis
4fb2e75c4e Fixes #3532 - Ticket Article Plain throws 204 Empty Content or HTTP 500 2025-05-27 12:38:20 +03:00
Mantas Masalskis
c231040855 Fixes #5176 - Report-Profile non-functional with organization is current users organization 2025-05-26 19:42:34 +03:00
Weblate
eb746424a3 Maintenance: Update translated files and catalog 2025-05-24 12:02:49 +01:00
Weblate
a2f0d327c4 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Suat VARLIKLI <suatvarlikli89@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translation: Zammad/Zammad (stable version)
2025-05-24 11:00:02 +00:00
Martin Gruner
020e85a98c Fixes #5415 - Enhance install script description for apache2 users about required modules 2025-05-23 14:48:15 +02:00
Weblate
3ec7dad837 Maintenance: Update translated files and catalog 2025-05-21 10:31:45 +01:00
Weblate
5e26794b08 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chrand818 <can@telenabler.com>
Co-authored-by: horizonxh <horizonxh@163.com>
Co-authored-by: sigurbjartur <github@sigurbjartur.com>
Co-authored-by: 周柏佑 <frank@sydt.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/is/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hans/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-05-21 09:26:36 +00:00
Rolf Schmidt
dfc90c43ec Fixes #5626 - Ticket zoom crashes when file is too large.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-05-20 17:01:43 +01:00
Martin Gruner
84bec2b759 Maintenance: Fix docker CI job 2025-05-20 11:19:43 +02:00
Dominik Klein
19252d6ebf Maintenance: Add vi.waitFor and vi.waitUntil wrapper functions to have a global default timeout. 2025-05-19 07:42:19 +02:00
Weblate
e71db20743 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translation: Zammad/Zammad (stable version)
2025-05-15 22:00:03 +00:00
Rolf Schmidt
e59be966de Fixes #5198 - Report Profile with selector "starts with one of" on text objects results in "unknown operator".
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-05-15 14:22:29 +01:00
Weblate
df5248ffb7 Maintenance: Update translated files and catalog 2025-05-15 10:01:58 +01:00
Weblate
ca7e972286 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fwmats <matsunaga@fairway-corp.co.jp>
Co-authored-by: sigurbjartur <github@sigurbjartur.com>
Co-authored-by: 周柏佑 <frank@sydt.com.tw>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/is/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ja/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2025-05-15 08:58:46 +00:00
Tobias Schäfer
298f6701d3 Draft: Fixes #5609 - Renaming tags is not working
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-05-15 09:58:53 +02:00
Rolf Schmidt
fc15103515 Fixes #5618 - getting "undefined method `assets' for nil:NilClass".
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-05-14 13:35:20 +02:00
Dusan Vuckovic
908dc7bea6 Fixes #5617 - When disabling two-factor authentication for one user, the... 2025-05-14 08:12:11 +01:00
Rolf Schmidt
51830cd462 Fixes #5613 - User merge fails if to merge user has Taskbars.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-05-13 16:57:01 +02:00
Rolf Schmidt
b77c301ab8 Fixes #5612 - Unexpected installation wizard visible when "init" request fails.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-05-13 14:07:35 +02:00
Florian Liebe
4d2f852a96 Fixes #5615 - OpenID Connect provider name not displayed on mobile. 2025-05-13 12:31:31 +02:00
Mantas Masalskis
534108fef7 Fixes #5606 - DataPrivacyTask fails, if user created or updated a ticket state 2025-05-13 13:04:47 +03:00
Weblate
ef2d568cfc Maintenance: Update translated files and catalog 2025-05-09 23:02:07 +01:00
Weblate
0f02fff972 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Dmjorge <duartemjorge@gmail.com>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_PT/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translation: Zammad/Zammad (stable version)
2025-05-09 22:00:02 +00:00
Florian Liebe
270c779bc0 Maintenance: Update rack to 2.2.14. 2025-05-09 07:22:37 +02:00
Dusan Vuckovic
ac52bf30f9 Fixes #5611 - Knowledge Base: Inserting animated images is unsupported again. 2025-05-08 17:13:54 +01:00
Florian Liebe
4cf012fdff Fixes #5604 - Login - Translation in French - bad escaping of quote characters.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-05-08 09:50:44 +02:00
Florian Liebe
1f7ec9fc8b Fixes #5605 - Integration > Exchange: dropdown field not limited in width. 2025-05-07 15:20:33 +02:00
Dusan Vuckovic
52ecbeb30d Fixes #5567 - M365 Graph: Sent email stored in authenticated user's 'Sent' items instead of shared mailbox. 2025-05-06 11:40:33 +01:00
Weblate
6ffe47ecdf Maintenance: Update translated files and catalog 2025-05-06 06:38:44 +01:00
Weblate
ad8f020b74 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Victor Pahuus Petersen <dibbohh@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: asrrd6 <ruslan.r.d@gmail.com>
Co-authored-by: fabiuxxlinuxx <fabiux@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/en_GB/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translation: Zammad/Zammad (stable version)
2025-05-06 05:36:24 +00:00
Dusan Vuckovic
4409c61516 Maintenance: Improve mobile autoredirect when system was not yet initialized. 2025-05-05 10:31:18 +01:00
Dusan Vuckovic
185b072a98 Fixes #5601 - Placeholder of search fields in the respective admin menus is partly in English if Zammad is set to German. 2025-05-02 09:48:19 +01:00
Rolf Schmidt
66588ec442 Maintenance: Make search columns for tickets more patchable. 2025-04-30 18:58:57 +02:00
Rolf Schmidt
783131e753 Fixes #5590 - Required value 'group_id' is missing during ticket creation via web.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-04-30 12:23:24 +02:00
Rolf Schmidt
efaf5fa4c6 Fixes #5600 - undefined method `each for nil:NilClass on login.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-04-30 12:14:23 +02:00
Rolf Schmidt
c775dc8391 Maintenance: Bump net-imap to 0.5.8.
Co-authored-by: Martin Gruner <mg@zammad.com>
2025-04-30 10:39:49 +02:00
Florian Liebe
3cf99e0525 Fixes #5588 - 'Last close at' is not translated in sorting dialog for overviews. 2025-04-29 16:20:48 +02:00
Rolf Schmidt
98fa920970 Fixes #5597 - Missing selected groups for multi tree select relation in ticket zoom.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-04-29 14:13:51 +02:00
Rolf Schmidt
9e1bcd8f70 Follow up c8ab4275 - Fixes #2297 - Also change store columns to type flattened to reduce impact on total fields. 2025-04-29 12:32:03 +02:00
Mantas Masalskis
3cea7a9a3e Fixes #5538 - Time Accounting report seems to be using incorrect timezone info 2025-04-28 17:11:47 +03:00
Rolf Schmidt
0f998168b2 Follow up 7b00c113e3 - Fixes #5571 - Add missing desktop view parts.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Dominik Klein <dk@zammad.com>
2025-04-25 15:08:42 +02:00
Martin Gruner
7cf3eef258 Fixes #5585 - Source installation via ftp.zammad.com not possible because of... 2025-04-24 14:15:41 +02:00
Rolf Schmidt
f371f7f989 Fixes #5571 - Time Accounting total rounds to 1 decimal point which is a...
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-04-24 13:47:00 +02:00
Rolf Schmidt
41f5a4f8c5 Fixes #5576 - D Keyboard Shortcut works without appearance rights.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-04-24 12:32:33 +02:00
Rolf Schmidt
402cfaca65 Fixes #5575 - Search empty for tickets with broken utf-8 (emojis) in article attachments.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-04-24 10:10:14 +02:00
Mantas Masalskis
f1074c08c7 Fixes #5531 - Too many WhatsApp notifications about service window closes 2025-04-24 07:49:42 +03:00
Dusan Vuckovic
ecdeea893c Fixes #5573 - Endpoint URL in webhooks being cut off after 300 chars. 2025-04-23 16:03:41 +01:00
Martin Gruner
12f75e399d Maintenance: Apply security update for nokogiri gem 2025-04-23 09:43:45 +02:00
Weblate
ffb86c6605 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translation: Zammad/Zammad (stable version)
2025-04-23 06:23:58 +00:00
Weblate
e1e0f17265 Maintenance: Update translated files and catalog 2025-04-22 12:51:58 +01:00
Weblate
6157436c7e Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Dmytro <ramadan2003@ukr.net>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Ville Perkkio <vilu@mail.suomi.net>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: daghanprojenet <daghan@projenet.com>
Co-authored-by: easyjoh <jh@easyplusplus.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fi/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (stable version)
2025-04-22 11:36:25 +00:00
Dusan Vuckovic
cc77c6318e Fixes #5564 - Microsoft 365 Graph Email channels do not tell which channel is being pulled during fetch. 2025-04-17 10:44:35 +02:00
Mantas Masalskis
8790d2ee3c Fixes #5062 - External Database field changes ticket without action 2025-04-16 18:59:24 +02:00
Dusan Vuckovic
d29f7c5596 Fixes #5562 - Having more characters in the maintenance field results in part of the characters escaping the field (mobile view). 2025-04-16 11:53:34 +02:00
Rolf Schmidt
2bb82a5af1 Fixes #5558 - Zammad 6.5 Elasticsearch issue with wildcards when using customer.organization_id:*. 2025-04-15 16:29:34 +02:00
Mantas Masalskis
0a84075a1b Fixes #4680 - Japanese emails importing incorrectly 2025-04-14 19:48:36 +02:00
Dusan Vuckovic
d1bc5b93ae Maintenance: Silence CSS syntax errors on build for now. 2025-04-14 16:46:10 +02:00
Rolf Schmidt
e88e828702 Fixes #5559 - Ticket search does not return tickets after the 10k element when searching with a query.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-04-14 10:48:00 +02:00
Rolf Schmidt
c2724a10d2 Maintenance: Stabilize ticket create test for taskbar tabs. 2025-04-11 17:54:49 +02:00
Rolf Schmidt
c56ee05105 Fixes #5556 - Core Workflow - Stop after match does disable the time accounting detection.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-04-11 14:24:25 +02:00
Rolf Schmidt
c8a525b650 Fixes #5529 - Admin Menu Search does not always clear the filter.
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-04-11 11:10:21 +02:00
Martin Gruner
32a5a0c277 Maintenance: Stabilize session invalid spec 2025-04-10 15:48:14 +02:00
Weblate
96334634d2 Maintenance: Update translated files and catalog 2025-04-10 12:31:41 +01:00
Weblate
66c9997c5a Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Dmytro <ramadan2003@ukr.net>
Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (pre-release version 6.5)
Translation: Zammad/Zammad (stable version)
2025-04-10 11:29:11 +00:00
Rolf Schmidt
0c105407e9 Fixes #5557 - Login is crashing when no connection to geo.zammad.com possible and service is configured.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-04-10 12:27:28 +02:00
Martin Gruner
50d1409427 Fixes #5553 - docker-compose start service error 2025-04-10 07:49:48 +02:00
Florian Liebe
8c52acaa50 Maintenance: Stabilize login spec when using SAML. 2025-04-09 10:31:04 +02:00
Benjamin Scharf
b11dbb338a Fixes #5547 - UI freeze when disabling checklists with not-found taskbar item
Co-authored-by: Benjamin Scharf <bs@zammad.com>
Co-authored-by: Rolf Schmidt <rs@zammad.com>
2025-04-09 09:24:56 +02:00
Dominik Klein
6efae0b9ba Maintenance: Add the possibility to run the performance test steps without exit on failure. 2025-04-08 09:49:36 +02:00
Rolf Schmidt
32465c1900 Maintenance: Ignore search index entries where database entries do not exist in search endpoint.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2025-04-07 17:19:49 +02:00
Florian Liebe
46bd8f106f Fixes #5554 - Custom payload for Microsoft Teams uses old/invalid template. 2025-04-07 15:48:16 +02:00
Florian Liebe
c038fa45b0 Fixes #5548 - Ticket article creation is using wrong ticket policy in GraphQL context.
Co-authored-by: Dominik Klein <dk@zammad.com>
2025-04-07 08:45:14 +02:00
Florian Liebe
42c2148fa1 Fixes #5532 - Error message during article creation is not clear enough (usage of mail security).
Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-04-04 22:47:41 +02:00
Florian Liebe
d6076e2c44 Fixes #5543 - X-Zammad-Ticket-Tags headers do not work for trusted channels.
Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-04-04 22:16:57 +02:00
Rolf Schmidt
0a247efa04 Fixes #5541 - The use of X-On-Behalf-Of in a REST call leads to a 500 error message.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-04-04 14:42:17 +02:00
Rolf Schmidt
f8994d6094 Follow up d2ca84a666 - Fixes #5504 - Make sure init data is only used once in ticket zoom. 2025-04-04 10:36:33 +02:00
Rolf Schmidt
6604b72eae Fixes #5504 - Performance: Preload taskbars before login.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2025-04-04 10:36:33 +02:00
Mantas Masalskis
61b4a196bf Fixes #5544 - Upgrade to Zammad 6.5 is not possible if a merged status is missing 2025-04-03 11:06:10 +02:00
Rolf Schmidt
adab1853e8 Maintenance: Switch tests to newer bitnami openldap version.
Co-authored-by: Martin Gruner <mg@zammad.com>
2025-04-02 14:04:02 +02:00
Martin Gruner
b616f6aa0a Maintenance: Apply security fix for omniauth-saml gem 2025-04-02 11:30:03 +02:00
Florian Liebe
568ebbedd1 Release Zammad 6.5.0. 🚀 2025-04-02 08:12:02 +02:00
Martin Edenhofer
c81b1eef4f Applyed workaround for #5541 - The use of X-On-Behalf-Of in a REST call leads to a 500 error message
(cherry picked from commit 777f463d7f)

777f463d Applyed workaround for #5541 - The use of X-On-Behalf-Of in a REST call leads...

Co-authored-by: Martin Edenhofer <me@edenhofer.de>
2025-04-02 04:53:55 +02:00
Weblate
1c09be9296 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: sbaecker <baecker@uni-koblenz.de>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translation: Zammad/Zammad (pre-release version 6.5)
2025-04-01 05:41:06 +00:00
Weblate
a911ea6a28 Maintenance: Update translated files and catalog 2025-03-31 06:52:21 +01:00
Weblate
0f6ba1064a Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Decio E. Silva <decio@verinatec.com.br>
Co-authored-by: Dmytro <ramadan2003@ukr.net>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chrand818 <can@telenabler.com>
Co-authored-by: sigurbjartur <github@sigurbjartur.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/is/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (pre-release version 6.5)
2025-03-31 05:47:22 +00:00
Martin Gruner
6831317507 Maintenance: Switch to Ruby 3.2.8 2025-03-27 14:39:18 +01:00
Weblate
b835c523a1 Maintenance: Update translated files and catalog 2025-03-27 12:23:37 +00:00
Weblate
9a22f3b275 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translation: Zammad/Zammad (pre-release version 6.5)
2025-03-27 12:19:19 +00:00
Mantas Masalskis
b1d014adb5 Maintenance: Improve Out-of-office test spanning DST switch 2025-03-25 14:45:06 +01:00
Rolf Schmidt
1381cc4e8b Maintenance: Ignore gitlab revert branches.
Co-authored-by: Martin Gruner <mg@zammad.com>


(cherry picked from commit 0659978995)

2212917f Maintenance: Ignore gitlab revert branches.

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-24 09:07:32 +01:00
Martin Gruner
7501080071 Maintenance: apply security update for nokogiri 2025-03-24 07:38:06 +01:00
Mantas Masalskis
14b274a351 Maintenance: Improve stability of capybara tests with browser time travels 2025-03-21 18:35:18 +01:00
Rolf Schmidt
4431b58ea3 Revert "Maintenance: Remove breaking changes for #5495, will move 6.6."
This reverts commit 939212703c
2025-03-21 14:41:27 +01:00
Martin Gruner
2fbe854ef9 Maintenance: stabilize packager.io tests 2025-03-21 10:27:56 +01:00
Rolf Schmidt
939212703c Maintenance: Remove breaking changes for #5495, will move 6.6. 2025-03-21 09:26:57 +01:00
Mantas Masalskis
697f3c5eb5 Fixes #5525 - Can't use Channel::Driver::Imap: #<ArgumentError: unknown... 2025-03-21 07:34:51 +01:00
Mantas Masalskis
62459de9fc Fixes #5523 - Email replies are still sent if channel is deactivated 2025-03-20 21:57:38 +01:00
Mantas Masalskis
4cf023fcb0 Maintenance: Improve pre-created email addresses and channels in test environment 2025-03-20 21:11:55 +01:00
Martin Gruner
0409a03cf3 Maintenance: Add caching for corepack in CI 2025-03-20 19:50:27 +01:00
Mantas Masalskis
4c1b04f844 Maintenance: Improve Knowledge base answers API 2025-03-20 18:44:32 +01:00
Dusan Vuckovic
eae5fbf42c Fixes #5524 - Payload gets lost when a pre-defined webhook is cloned. 2025-03-20 16:03:52 +01:00
Martin Gruner
8fb3e08692 Maintenance: Stablilize browser test jobs by disabling external services 2025-03-20 13:30:21 +01:00
Florian Liebe
87b40c59d9 Maintenance: Update Microsoft Teams webhook spec. 2025-03-20 11:47:39 +01:00
Rolf Schmidt
4e3fd8859b Maintenance: Add env for capybara host.
(cherry picked from commit 669df22c69)

669df22c Maintenance: Add env for capybara host.

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-20 11:04:43 +01:00
Dominik Klein
3157469414 Maintenance: Fixed typos in email driver files. 2025-03-19 21:48:56 +01:00
Mantas Masalskis
df7da4ac78 Fixes #5518 - Password reset mails not sent with active import_mode 2025-03-19 15:39:38 +02:00
Dusan Vuckovic
e377c56cee Fixes #5519 - Mobile View: Highlighting/border of activated profile menu is a square instead of a circle. 2025-03-18 14:00:19 +01:00
Rolf Schmidt
593469e050 Revert "Performance: Cache attribute records for object manager attributes handling."
(cherry picked from commit 9fa89b3d42)

1e015f75 Revert "Performance: Cache attribute records for object manager attributes handling."

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-18 11:16:10 +01:00
Martin Gruner
305ed40a50 Maintenance: Add breaking changes file from develop 2025-03-18 07:33:57 +01:00
Mantas Masalskis
b413157394 Fixes #5520 - Search pagination and sorting display is wrong when coming from another taskbar 2025-03-17 15:13:53 +01:00
Dusan Vuckovic
712dd8affb Fixes #5516 - Removing admin permission from a role makes Settings > System screen empty. 2025-03-17 13:17:21 +01:00
Martin Gruner
01ac049327 Follow-up fc01cb5ec9 - Maintenance: Optimize... 2025-03-17 11:08:50 +01:00
Weblate
d5fbb8469c Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chrand818 <can@telenabler.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translation: Zammad/Zammad (pre-release version 6.5)
2025-03-16 22:00:05 +00:00
Martin Gruner
18d25cb535 Maintenance: Optimize Chrome settings for CI usage 2025-03-16 18:53:02 +01:00
Mantas Masalskis
85621b75ac Fixes #5515 - Ticket merge dialog shows broken pagination 2025-03-14 19:14:46 +01:00
Rolf Schmidt
e1c3f67dfd Fixes #5240 - Incorrect very old log message for recursive trigger not executed.
(cherry picked from commit 13bf68792b)

bfd90212 Fixes #5240 - Incorrect log message for recursive trigger not executed; no...
cdebc802 fix wrong return

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-14 13:55:14 +01:00
Rolf Schmidt
d28538d74a Performance: Improve rendering for relation tree selects with >500 objects. 2025-03-14 09:31:28 +01:00
Rolf Schmidt
2d015252eb Performance: Cache attribute records for object manager attributes handling. 2025-03-14 09:31:16 +01:00
Rolf Schmidt
278d847f73 Maintenance: Make search params for relation list more patchable.
(cherry picked from commit 66d3b3f76f)

0c89c277 Maintenance: Make search params for relation list more patchable.
df1b6642 fix

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-14 09:21:46 +01:00
Martin Gruner
03ed62e92e Maintenance: Apply security updates for json and ruby-saml gems 2025-03-14 06:53:54 +01:00
Dominik Klein
6f90af12b0 Fixes #5514 - Migration for updating SAML help text is failing in some situations. 2025-03-13 12:00:58 +01:00
Martin Gruner
4e774a2890 Maintenance: Apply security update for graphql gem 2025-03-13 06:55:34 +01:00
Martin Gruner
3d93dedb02 Fallback to /proc/meminfo when lshw is unavailable 2025-03-12 13:36:57 +01:00
Mantas Masalskis
5b3504e854 Maintenance: Improve Ticket Shared Draft policy 2025-03-12 13:12:13 +01:00
Martin Gruner
198abe9293 Drop unused FTP CI service container 2025-03-12 12:05:31 +01:00
Mantas Masalskis
81faa6f165 Maintenance: Add Rubocop cop to ensure time zones in RSpec are set correctly 2025-03-12 11:13:05 +01:00
Martin Gruner
347a03641f Maintenance: Add thread name information to make thread dumps more useful 2025-03-12 09:34:10 +01:00
Martin Gruner
f41c682452 Maintenance: apply security update for rack gem 2025-03-12 07:52:27 +01:00
Martin Gruner
7a2569d7cf Fixes #5513 - Scheduler session nodes dying. 2025-03-11 12:14:23 +01:00
Rolf Schmidt
6c734230d6 Performance: Speed up set_fixed_to calls for bigger result sets.
Co-authored-by: Dominik Klein <dk@zammad.com>


(cherry picked from commit 28d8ec0ce2)

7bf68e87 Performance: Speed up set_fixed_to calls for bigger result sets.

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-11 10:27:21 +01:00
Rolf Schmidt
7e3b6f678a Fixes #5511 - Missing execution of core workflows when multiple create screens are opened on refresh.
(cherry picked from commit 863b417934)

5612df69 Fixes #5511 - Missing execution of core workflows when multiple create screens...
eada693e try loop instead
68bff62a cleanup loop

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-07 16:55:49 +01:00
Martin Gruner
f59b0a0b32 Fixes #5509 - Web concurrency setting inconsistently used/reported 2025-03-07 12:55:30 +01:00
Martin Gruner
9ff96f55df Maintenance: Include new session jobs env var in system report 2025-03-07 11:44:37 +01:00
Weblate
a0c0e64113 Maintenance: Update translated files and catalog 2025-03-07 06:07:51 +00:00
Weblate
bf5ef50af2 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: JPB <JPB@users.noreply.translations.zammad.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translation: Zammad/Zammad (pre-release version 6.5)
2025-03-06 22:00:08 +00:00
Mantas Masalskis
d37fc4eed4 Fixes #5505 - Tickets are sorted wrong in search 2025-03-06 16:38:56 +01:00
Florian Liebe
e1fdf81145 Fixes #5171 - Zammad sets an invalid Content-Type header for files with spaces in the filename.
Co-authored-by: Tobias Schäfer <ts@zammad.com>
2025-03-06 15:35:48 +01:00
Rolf Schmidt
18f82aef55 Maintenance: Make decision about self notification more patchable.
Co-authored-by: Florian Liebe <fl@zammad.com>


(cherry picked from commit d0884f47c9)

dfe330e5 Maintenance: Make decision about self notification more patchable.
88b3f874 fix missing param

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-06 11:12:10 +01:00
Weblate
040d0b75a5 Maintenance: Update translated files and catalog 2025-03-06 07:34:09 +00:00
Weblate
8fdfddc6bf Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Leif-Jöran Olsson <info@friprogramvarusyndikatet.se>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translation: Zammad/Zammad (pre-release version 6.5)
2025-03-06 07:21:36 +00:00
Martin Gruner
448b7e5902 Maintenance: Adapt docker test invocation 2025-03-06 08:19:17 +01:00
Martin Gruner
e1c1430f4a Maintenance: Apply security fix for rack gem 2025-03-06 07:11:49 +01:00
Tobias Schäfer
7342b7877a Fixes #4858 - Calendar non functional with activated 2fa authentication
Co-authored-by: Florian Liebe <fl@zammad.com>
2025-03-04 12:52:31 +01:00
Martin Gruner
a1dab8a8bb Maintenance: Improve github docker CI action 2025-03-04 10:33:55 +01:00
Rolf Schmidt
bb84c49e68 Follow up be06f0c - Fixes #5495 - Add global assets for ticket create screen.
Co-authored-by: Florian Liebe <fl@zammad.com>



(cherry picked from commit 269f97760d)

ab691973 fix requests for template and drafts.
fee9cb77 undo copy paste bug

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-03 20:45:23 +01:00
Rolf Schmidt
fde0613fcd Follow up be06f0c3881b3bfb447d487f5425a5cb598aa675 - Fixes #5495 - Remove obsolete requests for checklist templates.
Co-authored-by: Florian Liebe <fl@zammad.com>


(cherry picked from commit ac4aeb04c9)

912d5f0b Follow up be06f0c3881b3bfb447d487f5425a5cb598aa675 - Fixes #5495 - Remove...
204bc830 add render

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-03 20:45:08 +01:00
Rolf Schmidt
cb777e7cb7 Maintenance: Update cgi gem to 0.4.2 and uri to 1.0.3.
(cherry picked from commit dfc57d4797)

556411c0 Maintenance: Update cgi to 0.4.2.
e8db4279 Update uri gem.

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2025-03-03 19:55:37 +01:00
Mantas Masalskis
1062a9d365 Fixes #5499 - Drag´n´Drop of one attachment in the KnowledgeBase articles results in multiple copies 2025-03-03 15:31:56 +01:00
Mantas Masalskis
cf6d8b72c5 Maintenance: Stabilise custom object date validator spec 2025-03-03 12:52:39 +01:00
Weblate
36e3accbda Maintenance: Update translated files and catalog 2025-03-03 05:56:38 +00:00
Weblate
5847453164 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Christian Gravgaard <cg@bizperformer.com>
Co-authored-by: Jeroen Baten <jbaten@i2rs.nl>
Co-authored-by: Mantas Masalskis <mantas@idev.lt>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Nikita <nyakovlev@iko.tech>
Co-authored-by: Ralf Schmid <rsc@zammad.com>
Co-authored-by: Rok Poje <rok.poje@mintpos.si>
Co-authored-by: Solenne Lefevre <solenne.lefevre@data-bene.io>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fabiuxxlinuxx <fabiux@gmail.com>
Co-authored-by: sashkashurik <sashkashurik@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr_CA/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/lt/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translation: Zammad/Zammad (pre-release version 6.5)
2025-03-03 05:53:26 +00:00
Mantas Masalskis
8d4b3694e5 Fixes #5246 - Group selection not scrollable in bulk action with lot of groups 2025-02-28 15:17:27 +01:00
Rolf Schmidt
527c0bbf6c Follow up be06f0c3881b3bfb447d487f5425a5cb598aa675 - Fixes #5495 - Adjust... 2025-02-28 12:09:03 +01:00
Weblate
fb300d117d Maintenance: Update translated files and catalog 2025-02-28 05:21:34 +00:00
Weblate
ce06e08ee6 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Mantas Masalskis <mantas@idev.lt>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Rob <robertborge@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/lt/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nb_NO/
Translation: Zammad/Zammad (pre-release version 6.5)
2025-02-28 05:19:01 +00:00
Dusan Vuckovic
bb16fd5293 Maintenance: Update pnpm to v10 2025-02-27 19:09:32 +01:00
Dusan Vuckovic
29b69d1ea5 Fixes #5482 - Email Archive mode: Warn admins if they choose cut-off-date in the future. 2025-02-27 16:49:01 +00:00
Dusan Vuckovic
eb1eaf3f0a Follow up 340d9c10 - Fixes #5470 - Unify destination group setting and include e-mail-address in channel setup. 2025-02-27 15:30:07 +00:00
Florian Liebe
c1979c5ab7 Maintenance: Remove link to community thread from the M365 IMAP channel. 2025-02-27 14:27:30 +01:00
Martin Gruner
67d0f843af Maintenance: Fix failing docker builds 2025-02-27 11:28:30 +01:00
Martin Gruner
c21ad1b1e2 Maintenance: Stabilize future/past validator test 2025-02-27 08:23:51 +01:00
Dusan Vuckovic
8902d10c31 Fixes #4355 - Certain values in product_name will cause issues with sending agent notifications. 2025-02-26 17:33:31 +01:00
Martin Gruner
0348f6dd6d Maintenance: Improve docker CI configuration 2025-02-26 07:22:28 +01:00
Martin Gruner
10430cd1ce Maintenance: Improve configuration of packager.io CI jobs 2025-02-26 07:07:16 +01:00
Martin Gruner
a777172334 Maintenance: Improve configuration of packager.io CI jobs 2025-02-26 07:03:55 +01:00
Martin Gruner
8e7fea9624 Maintenance: Improve handling of webhook redirects 2025-02-25 09:51:33 +01:00
8075 changed files with 399923 additions and 1037594 deletions

View File

@@ -1,2 +1,2 @@
https://github.com/heroku/heroku-buildpack-nodejs.git#v371
https://github.com/pkgr/heroku-buildpack-ruby.git#v361-1
https://github.com/heroku/heroku-buildpack-nodejs.git#v280
https://github.com/pkgr/heroku-buildpack-ruby.git#v267-1

View File

@@ -1 +0,0 @@
../.dev/ai-agent-instructions.md

View File

@@ -1 +0,0 @@
../../.dev/agents/code-reviewer.md

View File

@@ -1,29 +0,0 @@
---
description: Start the full development workflow for a GitHub issue
---
# Work on issue
Work on the GitHub issue: $ARGUMENTS
The arguments may include an optional `--parent <github_issue_url>` flag,
indicating that this task is part of a larger story whose branch and MR
already exist. Parse this flag from `$ARGUMENTS` if present; everything
else is the task issue URL.
Follow the development lifecycle defined in
`.dev/agent_docs/development_workflow.md`. Do not skip phases:
1. **Understand** — Read the issue thoroughly. Summarize the scope,
acceptance criteria, and edge cases. Ask the user for clarification
if anything is ambiguous. If `--parent` was supplied, also read the
parent issue (it may be in a different repo) and list its sub-issues
for context on sibling tasks.
2. **Research** — Identify the affected areas of the codebase. Read
the agent reference docs from `.dev/agent_docs/` that apply to
those areas.
3. **Plan** — Present a plan to the user: what files will change, the
approach, potential side effects. Wait for approval before proceeding.
Stop after the Plan phase and wait for user approval before moving to
Branch / Implement / Test / Commit / Review / MR.

View File

@@ -1,66 +0,0 @@
#!/bin/bash
# Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
# Hook: lint all changed/new files with the appropriate tools.
# Collects modified and untracked files from git, then runs the matching linters.
# Claude Code invokes this script outside of an interactive shell, so rv's
# PROMPT_COMMAND-based version switching never runs; re-resolve it here.
if command -v rv >/dev/null 2>&1; then
RV_ENV=$(rv shell env bash) || { echo "rv shell env bash failed to resolve the Ruby environment" >&2; exit 2; }
eval "$RV_ENV"
fi
RUBY_FILES=()
FRONTEND_TS_FILES=()
FRONTEND_JS_FILES=()
COFFEESCRIPT_FILES=()
STYLE_FILES=()
MARKDOWN_FILES=()
EXIT_CODE=0
while IFS= read -r file; do
[[ -z "$file" ]] && continue
case "$file" in
*.rb) RUBY_FILES+=("$file") ;;
*.ts|*.vue) FRONTEND_TS_FILES+=("$file") ;;
# Legacy/vendored assets are third-party code and outside the scope of
# `pnpm lint:js`, which only covers app/frontend/ and .eslint-plugin-zammad/.
*.js) [[ "$file" == public/* || "$file" == app/assets/javascripts/* ]] || FRONTEND_JS_FILES+=("$file") ;;
*.coffee) COFFEESCRIPT_FILES+=("$file") ;;
*.scss|*.css) STYLE_FILES+=("$file") ;;
*.md) MARKDOWN_FILES+=("$file") ;;
esac
done < <(git diff --name-only --diff-filter=ACMR HEAD 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null)
FRONTEND_ALL_FILES=("${FRONTEND_TS_FILES[@]}" "${FRONTEND_JS_FILES[@]}")
if [[ ${#RUBY_FILES[@]} -gt 0 ]]; then
bundle exec rubocop --autocorrect "${RUBY_FILES[@]}" >&2 || EXIT_CODE=2
fi
if [[ ${#FRONTEND_ALL_FILES[@]} -gt 0 ]]; then
# Generated files (e.g. the GraphQL types) are excluded by the lint configs, so
# a changeset consisting only of those must not be treated as an error.
{ pnpm lint:js:oxlint:cmd --no-error-on-unmatched-pattern --fix "${FRONTEND_ALL_FILES[@]}" && \
pnpm lint:js:eslint:cmd --fix "${FRONTEND_ALL_FILES[@]}" && \
{ pnpm format:cmd "${FRONTEND_ALL_FILES[@]}"; _fmt_rc=$?; (( _fmt_rc == 0 || _fmt_rc == 2 )); }; } >&2 || EXIT_CODE=2
fi
if [[ ${#FRONTEND_TS_FILES[@]} -gt 0 ]]; then
pnpm lint:ts >&2 || EXIT_CODE=2
fi
if [[ ${#COFFEESCRIPT_FILES[@]} -gt 0 ]]; then
coffeelint --reporter=csv --rules ./.dev/coffeelint/rules/detect_translatable_string.coffee "${COFFEESCRIPT_FILES[@]}" >&2 || EXIT_CODE=2
fi
if [[ ${#STYLE_FILES[@]} -gt 0 ]]; then
pnpm lint:css:cmd --fix "${STYLE_FILES[@]}" >&2 || EXIT_CODE=2
fi
if [[ ${#MARKDOWN_FILES[@]} -gt 0 ]]; then
pnpm lint:md:cmd --fix "${MARKDOWN_FILES[@]}" >&2 || EXIT_CODE=2
fi
exit $EXIT_CODE

View File

@@ -1,42 +0,0 @@
#!/bin/bash
# Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
# Hook: regenerate generated files when their sources change.
# Claude Code invokes this script outside of an interactive shell, so rv's
# PROMPT_COMMAND-based version switching never runs; re-resolve it here.
# Needed before the pnpm generate-* calls below, which shell out to
# `bundle exec rails generate ...` and inherit this process's env.
if command -v rv >/dev/null 2>&1; then
RV_ENV=$(rv shell env bash) || { echo "rv shell env bash failed to resolve the Ruby environment" >&2; exit 2; }
eval "$RV_ENV"
fi
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMR HEAD 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null)
NEEDS_GRAPHQL=false
NEEDS_SETTINGS=false
while IFS= read -r file; do
[[ -z "$file" ]] && continue
case "$file" in
app/graphql/*) NEEDS_GRAPHQL=true ;;
app/frontend/*.graphql) NEEDS_GRAPHQL=true ;;
app/models/setting.rb) NEEDS_SETTINGS=true ;;
db/seeds/settings.rb) NEEDS_SETTINGS=true ;;
esac
done <<< "$CHANGED_FILES"
EXIT_CODE=0
if $NEEDS_GRAPHQL; then
echo "GraphQL schema changed — regenerating types..." >&2
pnpm generate-graphql-api >&2 || EXIT_CODE=2
fi
if $NEEDS_SETTINGS; then
echo "Settings changed — regenerating types..." >&2
pnpm generate-setting-types >&2 || EXIT_CODE=2
fi
exit $EXIT_CODE

View File

@@ -1,60 +0,0 @@
{
"permissions": {
"allow": [
"Bash(git log *)",
"Bash(git show *)",
"Bash(git diff *)",
"Bash(git blame *)",
"Bash(git branch)",
"Bash(git branch --show-current)",
"Bash(git status *)",
"Bash(git fetch *)",
"Bash(git checkout *)",
"Bash(git cherry-pick *)",
"Bash(glab *)",
"Bash(find *)",
"Bash(grep *)",
"Bash(bundle exec rubocop *)",
"Bash(RAILS_ENV=test VITE_TEST_MODE=1 bundle exec rspec *)",
"Bash(pnpm test *)",
"Bash(pnpm lint *)",
"Bash(pnpm generate-graphql-api)",
"Bash(pnpm generate-setting-types)",
"mcp__github__issue_read",
"mcp__github__list_issue_comments",
"mcp__codegraph__*"
]
},
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/lint-after-edit.sh",
"timeout": 120
}
]
},
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/regenerate-after-edit.sh",
"timeout": 120
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "pnpm exec codegraph prompt-hook"
}
]
}
]
}
}

View File

@@ -1,12 +0,0 @@
---
inheritance: true
reviews:
review_status: false
auto_review:
enabled: true
base_branches:
- develop
ignore_usernames:
- renovatebot
- zammad-weblate
drafts: false

View File

@@ -1,353 +0,0 @@
# Zammad Review Rules
Single source of truth for how an AI review decides what to flag, and how it says it.
This file is **tool-neutral on purpose**. It says nothing about `glab`, worktrees, GitLab APIs,
approval gates or output transport — those belong to whichever caller is using these rules.
It is also **not a duplicate of the coding conventions**. Its sibling files in this directory, and
`.dev/ai-agent-instructions.md`, are the conventions; this file tells a reviewer which of them to
read and how to turn a violation into a useful comment. When a convention changes, it changes there.
## Consumers
| Consumer | Lives in | Uses these rules for |
| ------------------------------- | ----------- | ------------------------------------------- |
| `.dev/agents/code-reviewer.md` | core repo | Reviewing changes in a fresh context |
| `code-review` skill | skills repo | Local review, delegates to the agent above |
| `review-mr` skill | skills repo | Reviewing a GitLab MR interactively |
When a rule changes, it changes **here** and every consumer picks it up. Do not copy rule text into
a consumer.
---
## Step 1 — Read the conventions for what the change touches
Before reviewing, identify which areas the diff touches and read **only** the applicable reference
docs. Do not read them all upfront, and do not review conventions from memory — these files are the
authority, and they change.
| The diff touches… | Read |
| ----------------------------------------------------- | --------------------------------- |
| GraphQL types, mutations, queries, subscriptions | `graphql_patterns.md` |
| Knowledge base code in the new stack | `knowledge_base_patterns.md` |
| Vue components, composables, routing, the form system | `frontend_patterns.md` |
| Addon extensions to the Vue frontend | `frontend_addon_layer.md` |
| New models used by the legacy CoffeeScript stack | `legacy_model_patterns.md` |
| Specs, test setup, test helpers | `testing.md` |
| Specs for a service or its GraphQL caller | `testing_services_and_graphql.md` |
| Service objects (`app/services/service/`) | `service_patterns.md` |
| Migrations or seeds | `database_migrations.md` |
A convention violation is a finding. "The surrounding code doesn't follow it either" is not a
defence — if the doc says it, the change should do it.
`.dev/ai-agent-instructions.md` carries the repo-wide rules that apply regardless of area:
- Every new file except Markdown files (`*.md`) needs the Zammad copyright header.
- `i18n/*.po` is never edited directly — translations come from translations.zammad.org.
- New features target Vue 3 + GraphQL. A new feature built on REST + CoffeeScript is worth
questioning unless it is extending existing legacy behaviour.
## Step 2 — Decide what is worth flagging
Precision matters more than recall. A review that reports five real problems is more useful than one
that reports five real problems buried in fifteen speculative ones, because the second teaches
people to skim.
Flag something only when **all** of these hold:
1. It meaningfully impacts correctness, performance, security or maintainability.
2. It is discrete and actionable — one specific problem, not "this area is messy" or a bundle of
related complaints.
3. It matches the rigor Zammad actually holds, which is high. A missing spec on new public
behaviour, an unchecked authorization path, or a bug fix without a regression test **is** a
finding — do not excuse it because neighbouring code is no better.
4. It was introduced by this change. Pre-existing problems are out of scope.
5. The author would plausibly fix it if they knew. If they would reasonably shrug, it is not a
finding.
6. It does not rest on unstated assumptions about the codebase or the author's intent.
7. It is not speculation. "This might break something elsewhere" is not a finding unless you can
name the affected code and show that it is affected.
8. It is clearly not a deliberate choice by the author.
**On volume:** report every finding that meets the bar — do not stop at the first one. If nothing
meets the bar, report nothing. "No findings" is a legitimate and useful outcome; inventing issues to
make a review look thorough is not.
Also out of scope in every review:
- Style and formatting a linter or formatter owns (RuboCop, ESLint, oxlint, Oxfmt, Stylelint),
unless it actively obscures meaning or breaks a documented standard.
- Naming preferences not covered by a convention doc.
- Speculative future requirements that are not in the issue.
- Pre-existing code visible in diff context but untouched by the change.
Verify before reporting: read the actual code, not just the diff hunk. Prefer evidence over
inference — if a working checkout is available, grep for related call sites and tests.
An honest "I cannot tell from the code" is a useful answer. Never manufacture confidence.
## Step 3 — Classify
Every finding gets **a category and a severity**. They are independent: the category says what kind
of problem it is, the severity says how much it matters.
**Category** (labels the type of finding):
| Prefix | Meaning |
| ------------ | ------------------------------------------------------------------ |
| `[security]` | Security issue introduced by the change |
| `[issue]` | Divergence from the linked issue's use cases or acceptance criteria |
| `[ui]` | Frontend convention regression |
| `[backend]` | Backend convention or correctness concern |
| `[test]` | Missing or inadequate test coverage for new behaviour |
| `[cleanup]` | Leftover artifacts, dead code, unnecessary complexity |
**Severity** (drives grouping and ordering):
| Level | Meaning |
| ----- | ------------------------------------------------------------------------------------ |
| P0 | Blocks. Security issue, broken functionality, missing authorization, data loss risk. |
| P1 | Should fix. Bugs, missing error handling, missing tests, convention violations. |
| P2 | Consider. Minor improvements, alternative approaches. |
Write them together: `[security] P0`, `[ui] P2`. Present findings grouped by severity in the
order P0, P1, then P2.
## Step 4 — Write the comment
1. Be clear about **why** it is a problem, not just that it is one.
2. **State the preconditions up front** — the inputs, environment or scenario needed for the problem
to occur, so the reader can judge severity immediately rather than inferring it.
3. Communicate severity accurately. Never inflate it.
4. One paragraph. The author should grasp it without close reading.
5. Be specific and actionable. Never vague feedback like "this could be better" — say what to do.
6. No code chunks longer than ~3 lines. Wrap code in inline code tags or a fenced block.
7. Matter-of-fact tone. Not accusatory, not congratulatory. No "Great job…", no "Thanks for…".
8. Anchor to the tightest line range that pinpoints the problem — 5–10 lines at most. Do not restate
the location in the body; the anchor already carries it.
9. One comment per distinct problem. Do not merge two issues into one, and do not post the same
issue at two locations.
### Suggestion blocks
Where the fix is mechanical, offer it as a suggestion the author can apply in one click:
````markdown
```suggestion:-0+0
the replacement line(s)
```
````
- Preserve the **exact** leading whitespace of the replaced lines — spaces vs tabs, and the count.
- Do not add or remove an indentation level unless that is the actual fix.
- Nothing but replacement code inside the block. Explanation goes in the paragraph above it.
- `-N+M` extends the range N lines above and M lines below the anchored line; `-0+0` replaces just
the anchored line. This differs from GitHub's suggestion syntax.
- Only when the fix is genuinely mechanical. If it needs a judgement call, describe it instead — a
wrong one-click fix is worse than prose.
## Step 5 — Report what you could not judge
Step 2 is precision-first on purpose: no speculation, nothing resting on unstated assumptions. That
makes "I cannot tell from the code" an honest and expected outcome — and since it produces no
finding, it is lost unless it has somewhere to go. It is often the most useful thing a reviewer
knows.
Report it separately from the findings: **at most three items**, each naming a place and the
question a human should answer there. Draw only on what the review actually produced:
- An `Unverifiable` acceptance criterion.
- A specific uncertainty with a named file — not "this might break something elsewhere".
- Something the change cannot show on its own: migration timing on a large installation, a rendered
result, `i18n/*.po` content, binary assets.
Two rules keep it honest:
- **Never park a finding here.** If it meets the bar in Step 2 it is a finding, with an anchor and a
severity. Moving a real problem into this list hides it from whoever has to fix it.
- **No items is the normal case.** Then write nothing — not a reassurance that there is nothing.
An open-ended list of things to double-check is a "check everything" disclaimer, and it trains
people to skim, for exactly the reason Step 2 exists.
Where this surfaces is the caller's business: a reviewer working in a terminal can print it for the
person running the review, while one posting to a merge request may deliberately keep it out of the
thread. It is guidance for the human reviewing, not a comment for the author.
---
## What to check
### Correctness
Logic errors, edge cases, off-by-ones, incorrect API usage, broken assumptions — anything that would
cause wrong behaviour or a crash. Also: **unintended side effects** — does the change affect code
paths that were not part of the plan?
### Security — `[security]`
OWASP-style issues **introduced by the change**:
- SQL / command / template injection
- XSS, unsafe raw HTML rendering
- Missing authorization or authentication checks (Pundit policies in `app/policies/`)
- Insecure deserialization
- Path traversal, SSRF
- Hardcoded secrets or credentials
- Weak or missing input validation at trust boundaries
- Unsafe use of `eval`, `system`, raw SQL, `send`/`public_send`, `constantize`/`safe_constantize`
on user-influenced input
### Issue alignment — `[issue]`
Only when a linked issue was found. Does the change cover the use cases the issue describes, and the
edge cases implied by them?
Acceptance criteria get their own pass below — do not fold them in here.
#### Acceptance criteria pass
When the linked issue lists acceptance criteria, this is a **closed checklist, not an open search**.
Work through it item by item rather than forming a general impression of whether the change "does
the right thing".
This pass is **exempt from the flagging threshold**: every criterion gets a verdict whether or not it
produces a finding.
- Enumerate **every** criterion verbatim, in the issue's own order. Do not merge, reword, skip or
summarise any of them — not even ones that look trivially satisfied.
- Give each criterion exactly one verdict:
- **Met** — demonstrably implemented. Cite the `file:line` that satisfies it.
- **Not met** — not implemented, or only partially. Each becomes an `[issue]` finding.
- **Unverifiable** — cannot be judged from code alone (needs manual testing, a screenshot, a
design review, or depends on code outside the change). State _why_. Never invent a Met/Not met
verdict just to avoid this outcome.
- Report as a table **every time**, including when everything is met — "all criteria verified" is
itself the answer the reviewer needs, not merely an absence of findings.
| # | Acceptance criterion | Verdict | Evidence |
| --- | -------------------- | ------------ | ----------------- |
| 1 | &lt;verbatim text&gt; | Met | `app/foo.rb:42` |
| 2 | &lt;verbatim text&gt; | Not met | not addressed |
| 3 | &lt;verbatim text&gt; | Unverifiable | needs manual test |
- If the linked issue has **no** acceptance criteria, say so in one line and move on. Do not invent
criteria from the issue's prose.
- A change with no findings but an unaddressed or unverifiable criterion is **not** clean.
#### Use-case cross-check
Criteria and use cases are written at different moments and drift apart. A criteria list can pass
completely while the story it was meant to describe is only half covered — so after the criteria
pass, map the issue's use cases / user stories onto its criteria.
Like the criteria pass, this is a **closed mapping between two lists the issue already contains**,
not a fresh search for behaviour the issue never asked for. Never invent a use case from prose.
Give each use case exactly one verdict:
| Verdict | Meaning |
| ----------------- | ---------------------------------------------------------------------- |
| Covered | a criterion asserts its observable outcome — cite the criterion numbers |
| Partially covered | an edge case, error path or role variant it describes has no criterion |
| Not covered | no criterion touches it |
| Conflicts | a criterion asserts something the use case contradicts |
What a gap means depends on the change, and the distinction decides who has work to do:
- **Not covered, but the change implements it** — not a finding. The change is fine; the issue's
criteria list is incomplete. Report it as that, and never attach it to the author's lines.
- **Not covered, and the change does not implement it** — an `[issue]` finding. This is the case
the cross-check exists for: it is invisible to the criteria pass, because there is no criterion
to fail.
- **Partially covered** — apply the same two cases to the uncovered aspect alone: an `[issue]`
finding when the change does not implement it, a gap in the criteria list when it does. Do not
re-litigate the part the criteria already cover.
- **Conflicts** — stop and ask. A self-contradictory issue is not resolved by quietly picking
whichever reading matches the change.
If the issue lists use cases but **no** acceptance criteria, there is no second list to map onto, so
the verdicts above do not apply — every one of them is defined against a criterion. Run the
acceptance-criteria pass over the use cases instead, exactly as written: each use case verbatim, one
verdict each (Met / Not met / Unverifiable), evidence as `file:line`, table every time. Label the
table as use cases, not criteria, so nobody reads it as something the issue asserted. Use cases are
descriptive rather than assertive, so a verdict against one is weaker evidence than a verdict
against a criterion — but dropping them entirely is worse. A contradiction _between_ two use cases
is still a stop-and-ask.
### UI principles — `[ui]`
Only when the change touches Vue components/templates, stylesheets, or translatable strings. These
expand the condensed list in `.dev/ai-agent-instructions.md`; read `frontend_patterns.md` for
everything else about Vue conventions.
They regress silently because the component still "works" — it just degrades accessibility, layout,
or translation quality for some users.
- **Icon-only action buttons** — a button/link whose only content is an icon (no visible text label)
must carry a `v-tooltip` directive that also supplies the accessible name (e.g. an `aria-label`
binding, or tooltip text that doubles as the label). Without it, screen reader users get an
unlabeled control.
- **Hover/focus-only actions on touch devices** — elements revealed only via CSS `:hover` or
`:focus-visible` (e.g. row actions appearing on table row hover) must still be reachable on touch
devices, which have no hover state. Flag unless there is a fallback (a `(hover: hover)` /
`(pointer: coarse)` media query, or the action is always shown at narrow viewports).
- **Container-based responsive grids** — a grid should adapt to the space actually available to it
(its container), not just the viewport: CSS Grid with `repeat(auto-fit, minmax(...))` or container
queries (`@container`), rather than a fixed column count that only changes at global viewport
breakpoints. Flag grids that would break or waste space inside sidebars, modals or split panes.
- **Translatable strings** — new or changed user-facing strings (`$t('...')` / `i18n.t('...')`,
locale keys) should:
- be complete and meaningful, avoiding single standalone words (e.g. `t('Name')`), since
translators lack the surrounding context to disambiguate them;
- use sentence case for English source strings (only the first word and proper nouns
capitalised), not Title Case;
- not duplicate an existing string. Before accepting a _new_ string, grep for the literal text in
other `$t(...)` / `i18n.t(...)` calls or the base locale file. If a near-identical string
exists, flag the new one as a duplicate and suggest reusing it — every distinct string adds
translator workload across all supported languages.
- **Danger styling and confirmations** — `danger` and `remove` (which counts as danger) mark the
click that cannot be undone, so they stay only on controls that act immediately. Flag a `danger`
or `remove` trigger whose handler ends in `waitForConfirmation` / `waitForVariantConfirmation`,
or opens a flyout that asks first. What replaces it depends on the kind of trigger: a `danger`
button becomes `tertiary` with an icon naming the action (`trash3` for delete and discard,
`arrow-counterclockwise` for reset); a menu item carries no variant at all, icon included; a
`remove` icon button loses the variant, keeps its icon, and takes the variant UX decides per
place (`tertiary` so far), so do not assume one. Flag a confirmation for irrecoverable loss
(deleting records, resetting user settings, discarding unsaved changes, clearing filters or
recent lists) whose confirm button is not `danger`: the `delete` and `unsaved` variants of
`CommonConfirmationDialog` already render one, so only the default `confirm` variant needs
`buttonVariant: 'danger'`. A control that asks only in some states (a dirty taskbar tab) follows
the state.
- Never flag a change to `i18n/*.po` as the fix. Those files are managed externally.
### Backend — `[backend]`
The conventions live in the sibling files routed in Step 1 — read those rather than reviewing Rails
practice from memory. Beyond them, the recurring concerns worth checking:
- **Query shape** — new associations rendered in a loop without `includes`/`preload` (N+1), a column
introduced for lookup or filtering without an index, queries inside view or serializer code.
- **Authorization** — new controller actions, GraphQL fields or mutations that reach data without
going through a Pundit policy. This is `[security] P0`, not `[backend]`, when data is exposed.
- **Background jobs** — jobs that are not idempotent, that carry state across a retry, or that
assume the record still exists when they run.
### Tests — `[test]`
New public behaviour should have tests: new public methods, new components, new endpoints, new
GraphQL fields. A bug fix should have a regression test that fails without the fix.
`testing.md` covers how tests are written and run here. Flag the gap; do not write
the test as part of a review.
### Leftover artifacts and cleanup — `[cleanup]`
- Debug code, `console.log`, `binding.pry`, commented-out blocks, TODOs introduced by this change.
- Dead code the change orphaned — a method that is now unused, an import that is now unreferenced.
- Obvious duplication introduced by the change, and unnecessary complexity.
This matters most when reviewing an agent's own output, where scaffolding tends to survive into the
diff.

View File

@@ -1,54 +0,0 @@
# Database Migrations & Seeds
## Creating a Migration
```bash
rails generate migration IssueNumberShortDescription
```
The generator template automatically includes the `system_init_done` guard
(`return if !Setting.exists?(name: 'system_init_done')`), which skips
data-migration logic on fresh installations where seeds handle setup.
Zammad migrations can include data migrations, not just schema changes.
- Call `Model.reset_column_information` after adding/removing columns
in the same migration
## Schema Changes
When modifying table structure, also update the base migrations
so fresh installations get the same schema (migrations are cumulative,
but base files are the canonical schema source for new installs):
- `db/migrate/20120101000001_create_base.rb`
- `db/migrate/20120101000010_create_ticket.rb`
Conventions:
- Use `limit: 3` for timestamp precision
- Specify `type: :integer` for polymorphic references
- Use `id: :integer` in `create_table` calls (Zammad uses integer primary keys, not bigint)
- Add indexes for frequently queried columns
## Settings
New or updated settings must be added in both the migration and the seeds.
See existing examples in `db/seeds/settings.rb` for the correct structure.
## Seeds
Seeds live in `db/seeds/` and use idempotent methods like
`create_if_not_exists` to avoid duplicates. When adding user-facing fields,
also update `db/seeds/object_manager_attributes.rb`.
## Helpers
`MigrationHelper` (`lib/migration_helper.rb`) provides utilities for
renaming custom object attributes and handling reserved SQL words.
## Testing
```bash
rake db:migrate # Test the migration
rake db:rollback STEP=1 # Undo last migration (schema only — data migrations are not reversed)
```

View File

@@ -1,191 +0,0 @@
# Development Workflow
## Issue Tracking & Code Hosting
- Issues (bugs, stories, epics) are tracked on **GitHub** — in `zammad/zammad`
(public) and private repositories (e.g. `zammad/coordination-desktop-view`).
- Code is developed on a **self-hosted GitLab** instance.
- GitHub hosts a **read-only mirror** — synced automatically when public branches
like `develop` or `stable` are updated.
- Branches prefixed with `private/` are **never synced** to GitHub.
All branches must start with `private/` to prevent unintended syncing.
## Tools
- **GitHub MCP server** — for reading issues, labels, and comments.
- **`glab` CLI** — for pushing branches, creating merge requests, and other
GitLab operations. Authenticated against the self-hosted GitLab instance.
## Before the lifecycle: the story
The lifecycle below starts at "read the GitHub issue" and assumes a task
issue already exists that says something useful. Getting there is its own
stage, and it acts on a **story** rather than on a task:
1. **Refine** — `/refine-story`. Checks whether the story can be planned at
all, whether it is really several stories, and whether its acceptance
criteria, its use cases and its linked design agree. Proposes the missing
criteria and sections, and writes them to the story on confirmation.
2. **Break down** — `/plan-story`. Cuts a ready story into `Task` and `Spike`
sub-issues: a task carries the plan of how to solve it, a spike the question
it has to answer. Refuses to plan a story that is not ready.
3. **Present** — `/present-story`. Explains the prepared work in a sprint
planning. Reads only.
The rules all three follow — the definition of ready, how story size is
judged, the closed list of layers a plan must give a verdict on, and how deep
a plan may go — are in `.dev/agent_docs/story_planning_rules.md`, the
counterpart of `code_review_rules.md` at the other end of the lifecycle.
**A bug does not go through this stage.** It enters the lifecycle directly.
A bug report has no user story and no acceptance criteria by design — the
template asks for actual and expected behaviour and reproduction steps
instead — so the definition of ready does not apply to it, and the story
skills report that rather than demanding something a bug can never have. The
exception is a bug so large it needs splitting, and that is worth a
conversation about whether it should be a story.
Note that **"Plan" means two different things**: step 3 of the lifecycle below
is the technical plan for one task, written by whoever implements it.
`/plan-story` is the story-level breakdown that produces those tasks.
## Lifecycle
### 1. Understand
Read the GitHub issue thoroughly before writing any code. Clarify scope,
acceptance criteria, and edge cases. If the issue is ambiguous, ask the user
for clarification rather than guessing.
**Determine the mode.** If the user supplied `--parent <github_issue_url>`,
this is **attach mode**: the task is part of a larger story whose branch and
MR already exist. Otherwise, this is **standalone mode**: the task gets its
own branch and MR. The parent may live in a different repository — parse
owner + repo + number from the URL, do not assume same-repo.
In attach mode, also:
- Read the parent issue via the GitHub MCP server (it provides context the
task issue alone doesn't have).
- List the parent's sub-issues. Note which are already closed, which are
in progress, and who is assigned — this prevents stepping on a colleague's
work.
- Confirm the existing branch is already checked out in the current workspace
(Conductor creates the workspace from a branch; the branch is on disk
before the workflow starts). If it is not — abort and ask the user.
Then determine **which parts of the codebase the issue affects**:
- Check labels and comments for scope hints (when available).
- Otherwise, search the codebase for keywords from the issue title and body
to locate the relevant component(s).
- Identify which stack is affected: CoffeeScript frontend, Vue 3 frontend
(desktop or mobile), REST backend, GraphQL layer, services, etc.
- State your conclusion explicitly before moving on, e.g.
"This issue affects the CoffeeScript frontend (KB answers component) and
the REST controller behind it. I will NOT investigate Vue 3, GraphQL, or
unrelated services unless something during research requires it."
This explicit scoping prevents broad codebase exploration in areas the
issue does not touch.
### 2. Research
Investigate only the parts of the codebase identified in the Understand
phase. Delegate broad research to separate agents to keep your main context
clean. Understand the existing patterns before introducing new ones.
If during research you discover the scope was wrong (e.g. the data turns
out to come from a service you hadn't considered), update the scope
explicitly and tell the user before continuing.
### 3. Plan
Identify affected files and outline the approach. For non-trivial changes,
present the plan to the user before implementing. Consider side effects on
other parts of the system.
### 4. Branch
**Standalone mode:** Use the `/prepare-issue-branch` skill to create the
branch and prepare the commit message. It handles naming conventions, public
vs. private issue detection, and commit message formatting automatically.
**Attach mode:** Do _not_ invoke `/prepare-issue-branch`. The story branch
is already checked out (confirmed in step 1). Instead:
- Run `git fetch origin && git pull --rebase` to align with what colleagues
have pushed since you started.
- If `git pull --rebase` produces conflicts, stop and ask the user to
resolve them before continuing.
- Do not prepare a commit message yet — it will be written in step 8
based on what was actually changed.
### 5. Implement
Write the code following the patterns described in the relevant agent reference
docs (graphql_patterns, frontend_patterns, service_patterns, etc.). Read the
relevant doc before starting implementation — do not read them all upfront.
### 6. Test
Run the specific tests affected by your changes. Write new tests for new
behavior. Never run the full test suite — CI handles comprehensive testing.
### 7. Review
Before committing, delegate the review to the `code-reviewer` sub-agent.
The sub-agent has its own context window and reviews the uncommitted diff
without bias from the implementation history.
The sub-agent will report findings grouped by severity (P0/P1/P2). If
findings are reported, return to step 5 (Implement) to address them, then
test again before re-running the review.
If the sub-agent reports no findings, proceed to commit.
### 8. Commit
Pre-commit hooks run linting and validation automatically. If you changed
the GraphQL schema or settings, the regenerate hook runs the appropriate
generators automatically.
**Standalone mode:** Use the prepared commit message from step 4
(`/prepare-issue-branch` wrote it to `.git/COMMIT_EDITMSG`).
**Attach mode:** Write a commit message that describes what this commit
changed — no issue reference, no `Closes`/`Fixes` keywords. The parent
story's MR carries the issue context; individual commits within it only
need to describe their own change. Keep it short and conventional, e.g.
`Add validation for empty subject on ticket form.`
### 9. Merge Request
**Standalone mode:** Use the `/create-mr` skill to push the branch and
create a Merge Request on GitLab. It selects the correct MR template, fills
in the sections, and targets the appropriate branch automatically.
**Attach mode:** Do _not_ invoke `/create-mr` — the MR for the parent story
already exists.
**Don't push automatically.** After committing, stop and wait. Tell the
user what you committed and ask before pushing — every time. Pushing
sends your commit to the team's MR and runs CI, so the user decides
when that happens.
Push only after the user confirms.
CI runs automatically on MR creation (standalone) or push (attach). A human
review is required before merging.
### 10. Cherry-pick
If the fix also applies to `stable`, use the `/cherry-pick-to-stable` skill
after the MR is merged to `develop`.
## Rules
- Never push directly to `develop` or `stable`.
- Never reference private issue numbers in public commits.
- Never run the full test suite — always target specific files.

View File

@@ -1,107 +0,0 @@
<!-- Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/ -->
# Frontend addon layer
How an external addon (szpm package) extends the new Vue frontend — without
overwriting core files, without runtime monkey-patching, and in a way that
survives the planned Vapor migration.
## Mechanism: build-time weave
An addon's UI changes are applied by **rewriting the targeted core SFC's source
text at build time, before the Vue compiler runs** — a Vite `enforce: 'pre'`
plugin in `app/frontend/build/addonWeave` (see its `README.md` for internals).
It is Vapor-safe by construction: it edits the compiler's _input_ (template
source), not the vnode tree at runtime, so whatever compiler runs next compiles
the merged source normally — the vdom compiler today, the Vapor compiler later.
(Runtime monkey-patching — wrapping `render`, walking/cloning vnodes — is the
opposite: automatic but vdom-only, and cannot work under Vapor.) Core SFCs are
never modified on disk; only the in-memory module is rewritten during the build.
## Multi-addon: per-addon manifests, discovered
There is **no central rule list**. Each addon ships its own `*.weave.mjs`
manifest next to its code; `discoverRules.mjs` globs every manifest under
`app/frontend` and concatenates them. Core names no addon — adding one is
dropping a manifest + its code, never editing a shared core file. Any number of
addons compose.
## Manifest format
`…/<addon>.weave.mjs` (plain JS — it is imported at config time):
```js
export const addonWeaveRules = [
{
target: 'shared/components/CommonOrganizationAvatar/CommonOrganizationAvatar.vue',
scriptSetup: [
"import { useThing } from '#shared/addons/<addon>/useThing.ts'",
'const __addonThing = useThing(() => props.entity)',
].join('\n'),
template: [
// decorate the element (class / style / attribute)
{ match: { component: 'CommonAvatar' }, addAttribute: ':class="__addonThing"' },
// inject a sibling before / after a matched element
{ match: { hasClass: 'addon-anchor-ticket-reply' }, insertBefore: '<MyBanner />' },
],
},
]
```
- `target` — core SFC path (suffix match); stays byte-for-byte untouched.
- `scriptSetup` — code appended inside the SFC's `<script setup>` (imports +
setup), referencing the addon's own files via `#shared` / `#desktop` aliases.
- `template` — semantic edits on the parsed template AST (not raw text). Each op
is a `match` plus an action: **additive** (composable) `insertBefore` /
`insertAfter` (sibling), `prepend` / `append` (first/last child), `addAttribute`,
`setAttribute` / `removeAttribute` (override/remove `name` or `:name`),
`wrap: { open, close }`; or **destructive** (stands alone) `replace` / `remove`.
`match` accepts `component` / `element` / `tag` / `id` / `testId` / `hasClass` /
`attr: { name, value? }` / `root` (AND-combined, component≠native) and must hit
≥1 element; overlapping edits — even across addons — fail the build too.
The addon's logic (composables/components) lives in its own files. Data usually
comes from the Apollo cache (`objectAttributeValues` etc. — a stable public
GraphQL contract), so an addon rarely needs anything new from core.
## Testing
- **Vitest** — render the **pristine** core component; the weave runs during the
test build (the plugin is in `vite.config.mjs`), so the woven behavior is what
you assert. Seed the Apollo cache via the mock client for cache-backed
composables.
- **For addons, prefer Capybara/system tests** — they exercise the package in the
real, built app (the weave actually applied on install), which is the addon's
main risk. Vitest is the faster secondary check.
- A test runs in CI **only if registered** in the addon's `.gitlab-ci.yml`
`script` (and listed in the `.szpm` `files`); CI does not auto-discover specs.
## Gotchas
- **Discovery roots on `process.cwd()`, not `import.meta.url`** — Vite bundles
`vite.config.mjs` (and its imports) to a temp file before running, so an
`import.meta.url`-relative path resolves wrong and the glob finds nothing.
- A newly linked addon's woven SFC can be stale in a **warm Vite cache** (the
pristine SFC source never changed, so only a config-dependency change busts
it). A fresh build / CI run is unaffected.
- Avatar/icon colors: use the desktop **design tokens** (`var(--color-…)`), not
hard-coded hsl/rgb.
- A **linked** package needs `PRESERVE_SYMLINKS=1` _and_ pnpm
`nodeLinker: hoisted`: without the first, `#shared/*` (a subpath import of
`app/frontend/package.json`) does not resolve from the symlinked addon file;
without the second, pnpm's symlinked `node_modules` breaks transitive
resolution. `zammad:package:pnpm_hoisted_mode` covers `assets:precompile`,
not `pnpm dev`.
## Honest scope
- **Vapor itself is not yet exercised** (this Vue has no Vapor compiler); the
Vapor-safety is by construction and must be confirmed once the repo builds
Vapor.
- Matching is AST-based (component/element identity, `id`, `data-test-id`, a
static class, or the root) and edits splice at exact source offsets; a missed
anchor **fails the build loudly**. A failing weave fails the _whole_ build —
there is no per-addon isolation (intended: a failed install over a broken UI).
- Manifest discovery globs `app/frontend`; the real source of truth should be the
installed-package (szpm) list.

View File

@@ -1,32 +0,0 @@
# Frontend Patterns
## Code Placement Rules
Code shared across both apps (desktop/mobile) lives in `app/frontend/shared/`.
Code used across multiple pages within one app goes at the app level.
Page-specific code stays in the page folder.
**Pages cannot import from other pages** (enforced by eslint).
Move shared logic up to the app or shared level.
## shared/entities/
Business logic per domain entity (e.g. ticket, user) lives in
`shared/entities/<entity>/`. This includes entity-specific composables,
GraphQL operations, object attribute stores, and types.
Do not place components inside entity folders.
## shared/components/ — CommonXYZ Convention
Shared components follow the `CommonXYZ` naming pattern.
The folder name, Vue file name, and component name must match
(e.g. `CommonNotifications/CommonNotifications.vue`).
Related composables, types (`types.ts`), and sub-components live
inside the same folder.
## Auto-Generated Files — Never Edit
- `shared/graphql/types.ts`
- `shared/types/config.ts`
- `.ts` files next to `.graphql` operation files
(e.g. `graphql/queries/`, `graphql/mutations/`, `graphql/subscriptions/`)

View File

@@ -1,72 +0,0 @@
# GraphQL Patterns
Knowledge base queries, mutations and subscriptions follow extra rules on top of
these, see `.dev/agent_docs/knowledge_base_patterns.md`.
## Resolver Structure
Resolvers are thin wrappers. Business logic lives in `app/services/service/`:
```ruby
def resolve(...)
Service::SomeAction.execute(...)
end
```
Their specs follow the same split: the behavior is covered by the service
spec, the GraphQL spec only covers the GraphQL surface — see
`.dev/agent_docs/testing_services_and_graphql.md`.
## Authorization
Multiple layers, applied declaratively:
- `requires_permission('ticket.agent')` — static permission check at resolver level
- `allow_public_access!` — opt-out of authentication
- `loads_pundit_method: :update?` — authorize loaded objects via Pundit
at argument level
- `requires_enabled_setting('some_feature')` /
`requires_disabled_setting(...)` — feature flag guards
Object-level authorization uses Pundit via `HasPunditAuthorization`
concern on types.
## Auto-Registration
Queries, mutations, and subscriptions are auto-registered — no manual
field registration needed. Just create the class inheriting from the
appropriate base and it will be discovered.
## Custom Relation DSL
Types use custom macros (not standard graphql-ruby):
```ruby
belongs_to :group, Gql::Types::GroupType
has_one :organization, Gql::Types::OrganizationType
```
These automatically set up batch-loaded resolvers and mark fields as
dependent for permission checks.
## Scoped Fields
```ruby
scoped_fields do
field :some_field, String, null: true
end
```
Fields inside `scoped_fields` are restricted by Pundit FieldScope —
they return `nil` instead of errors when unauthorized. Must be nullable.
## Internal Fields
```ruby
internal_fields do
field :some_field, String
end
```
Fields inside `internal_fields` are restricted by checking for admin/agent permission.
They return `nil` instead of errors when unauthorized.

View File

@@ -1,76 +0,0 @@
# Knowledge Base Patterns
**Scope: the new stack only** — the Vue desktop app
(`app/frontend/apps/desktop/pages/knowledge-base/`), the GraphQL layer
(`app/graphql/gql/**/knowledge_base*`) and the services behind it
(`app/services/service/knowledge_base/`). Nothing here describes the other two
consumers: the legacy CoffeeScript admin frontend, and the server-rendered
public help site (`app/controllers/knowledge_base/public/`) — which is not
legacy, just a different stack. Both work on the knowledge base directly and by
their own rules, so do not apply these rules to them, and do not "align" them
with these.
For the layers themselves see `.dev/agent_docs/graphql_patterns.md` and
`.dev/agent_docs/service_patterns.md`.
## A single, active knowledge base
The system supports exactly one knowledge base, and the new stack works on it
only while it is **active** — browse queries, feeds, subscriptions and write
mutations alike. An inactive one is neither browsable nor editable there;
activating it and changing its settings (not its texts) stays with the legacy
admin dialog, which edits the record directly.
Rules for new code:
- Never accept a knowledge base id from the client, and never scope anything by
one. Resolve it: `::KnowledgeBase.active.first!`.
- Raise `ActiveRecord::RecordNotFound` when none is active, rather than
answering with null or an empty list. Whoever resolves the record asserts it —
a write service does it for itself, a resolver with no service behind it does
it inline.
- Do not pass a `knowledge_base:` into a write service. Pass it to a read
service only if the resolver had to resolve the record anyway; nothing
resolves it twice.
- Pass the client's `locale` code straight to the service, which resolves it
strictly against that knowledge base (a `KnowledgeBase::Locale` record is
accepted too), then call `store_knowledge_base_locale` so the payload renders
in the locale that was written.
The single-knowledge-base assumption rests on there being no create route
(`resources :knowledge_bases, only: %i[show update]`) and the legacy admin
dialog offering its creation form only while none exists. It is not enforced by
a validation or a database constraint, so `first!` picks arbitrarily if seeds or
a console ever create a second one.
Where the rules live today — current code, not itself a rule: the write services
resolve the record inside `Service::KnowledgeBase::Base#active_knowledge_base!`.
Everywhere else the resolver resolves it, then either hands it to a read service
(`CategoryContent`, `FeedPaths`) or — where no service sits behind the
operation — asserts it inline and works on the record itself. Grep
`KnowledgeBase.active.first` to see which operations do which; it is a moving
set, so do not rely on a list here.
## Deliberate exceptions
Do not "fix" these without a decision:
- `Gql::Subscriptions::KnowledgeBase::ContentUpdates` pings with a **null**
knowledge base instead of raising — a subscription cannot raise usefully, and
this ping is the only signal browse views get after a deactivation. They
refetch, then hit the not-found from the queries.
- `KnowledgeBasePolicy#show_any?` and `KnowledgeBasePolicy::Scope` let granted
access outrank the active check, because the public help site previews an
inactive knowledge base that way — the policy for anyone with effective access
to the record (a reader included), the scope for the `knowledge_base.editor`
permission.
- `Gql::Concerns::SearchesKnowledgeBaseAnswers` answers with an **empty result**
instead of raising when no knowledge base is active. It is reached from a
document that asks for more than the knowledge base — the desktop quicksearch
requests every searchable entity in one query — and the search field is
`null: false`, so a raise would null the root and take the ticket, user and
organization groups down with it on every instance that has no knowledge base.
The knowledge base's own queries, `Gql::Queries::KnowledgeBase::Search`
included, do raise: they are asked for on their own.
Each of these carries the reasoning in a code comment; keep the two in sync.

View File

@@ -1,92 +0,0 @@
# Model Patterns for the Legacy CoffeeScript Stack
When introducing a new model that is used by the legacy stack (e.g. a generic
admin interface), two concerns are needed so the frontend can render and stay
in sync: REST assets and client notifications.
## REST Assets
### What assets are
REST responses for the legacy stack ship related records ("assets") alongside the
requested objects, so the frontend Spine store can render relations without extra
requests. `ApplicationModel::CanAssets` provides the default `assets(data)`
implementation; models only need their own `Model::Assets` concern when the default
is not enough.
### When to add a custom assets concern
- The model has `belongs_to` relations whose records the UI needs to render
(e.g. a user reference or a polymorphic relation).
- The model needs to filter attributes or restrict visibility per user
(see `app/models/group/assets.rb` — `filter_unauthorized_attributes` /
`authorized_asset?`).
### Rules
- Only follow one-to-one (`belongs_to`) relations. NEVER include one-to-many
relations — e.g. organization members can be a million records; shipping them
as assets must be prevented (`app/models/organization/assets.rb` limits this).
- Guard against `nil` relations: optional `belongs_to` and polymorphic targets
whose record has been deleted return `nil`.
- Keep the dedup guard (`return data if data[app_model][id]`) — it prevents
infinite loops between mutually referencing assets.
- Include the concern in the model: `include Model::Assets` (after the generic
concerns block).
### Skeleton
Minimal concern following `app/models/mention/assets.rb` (belongs_to user +
polymorphic relation):
```ruby
class MyModel
module Assets
extend ActiveSupport::Concern
def assets(data)
app_model = self.class.to_app_model
data[ app_model ] ||= {}
return data if data[ app_model ][ id ]
data[ app_model ][ id ] = attributes_with_association_ids
if my_polymorphic_relation.present?
data = my_polymorphic_relation.assets(data)
end
return data if user.blank?
user.assets(data)
end
end
end
```
### Reference examples
- `app/models/mention/assets.rb` — user + polymorphic `mentionable`
- `app/models/online_notification/assets.rb` — lookup tables + created/updated by
- `app/models/group/assets.rb` — attribute filtering and per-user authorization
## Client Notifications
Models rendered by the legacy stack (admin interfaces, collections) must
broadcast changes to connected clients, otherwise open sessions show stale
lists until a manual reload:
```ruby
class MyModel < ApplicationModel
include ChecksClientNotification
end
```
- `app/models/concerns/checks_client_notification.rb` pushes
`MyModel:create/update/touch/destroy` events (id + updated_at) via
`PushMessages`; the frontend Spine collections then refetch.
- Default recipient type is `authenticated` (all logged-in clients); actual
record access is still enforced on fetch by the controller policy.
- Restrict recipients with `client_notification_send_to :user_id` (only the
referenced user) or skip events with
`client_notification_events_ignored :touch, ...`.

View File

@@ -1,188 +0,0 @@
# Zammad Product Principles
What the product holds to be true, in a form a story can be checked against.
Every principle is an **assertion plus what it rules out**. The second half is what does the work: it
says what a violation looks like. A statement nothing could violate — "we value a consistent user
experience" — is decoration, and it makes this file worse, because it trains readers to skim and it
lets a tool generate findings nobody can act on.
This file is **not** the coding conventions. Its sibling files in this directory are the conventions;
this file is about the product, and most of its content was extracted from decisions the team had
already taken and documented once, in `BREAKING_CHANGES.md` or in a single story, where the next
story could not find it.
## Consumers
| Consumer | Lives in | Uses these principles for |
| -------------------- | ----------- | ------------------------------------------------------------ |
| `refine-story` skill | skills repo | Checking a story and its design against the product's stance |
## How to cite and change
- IDs are **section-prefixed and append-only**: `CHANGE-1`, `UI-5`. Cite as
`.dev/agent_docs/product_principles.md#CHANGE-1` — the repository-relative path, because most
citations are written from outside this directory.
- The `#ID` suffix is a **stable citation token, not a Markdown anchor**. The IDs are bold list
items, and an explicit anchor would need inline HTML, which this repository's `markdownlint`
configuration rejects.
- A citation therefore resolves by **searching for the list item that defines the ID** — a line
starting `- **` followed by the ID and then either `**` or a space, as in `- **CHANGE-1**` or
`- **UI-6 (proposed)**`. That line is unique for every ID in this file. Searching for the bare ID
is not deterministic: it also matches this section, and it prefix-matches a future `CHANGE-10`.
- Do not "fix" the missing anchor by promoting the IDs to headings. A heading anchor is derived from
the heading text, so it would break on every wording change — the opposite of what the next rule
guarantees.
- **Never renumber.** The change and dependency principles get cited outside this file — in a
`BREAKING_CHANGES.md` entry, a review comment, a refinement note — so an old citation has to keep
resolving.
- A principle that no longer holds keeps its ID and is marked `(retired)` with the date. It is not
deleted.
- Anything genuinely undecided goes in **Unsettled** at the bottom rather than being guessed at. A
consumer reading this file must be able to tell "we decided against it" from "we never decided".
---
## How we change things
Owner: development · last reviewed 2026-08
- **CHANGE-1** — Nothing is removed, renamed or restricted without a deprecation announced at least
one release earlier. _Violation: a removal whose first appearance in `BREAKING_CHANGES.md` is the
release that removes it._
- **CHANGE-2** — Every breaking change states who is affected, what they have to do, and links a
public issue. _Violation: an entry that describes the change but not the action, or that has no
public issue to point at._
- **CHANGE-3** — Only versions the vendor still maintains are supported, for every dependency —
database, search index, Redis, Node, browsers, reverse proxy. The supported matrix stays small and
predictable for administrators. _Violation: keeping compatibility with an end-of-life version in
order to avoid a breaking change._
- **CHANGE-4** — Prefer one general mechanism over several bespoke ones. Where a general mechanism
covers what a special-cased feature did, the special case is a candidate for removal. This is a
preference and a judgement call, not an automatic verdict: _a new special case for something an
existing general mechanism already does is worth questioning, not automatically wrong._
- **CHANGE-5** — Every removal names its replacement, or states explicitly that there is none.
_Violation: "X was removed" with no forward path and no admission that there isn't one._
## Access and permissions
Owner: development · last reviewed 2026-08
- **ACCESS-1** — Access is least-privilege and explicit, and tightening it is worth a breaking
change. _Violation: a new endpoint, field or action that is reachable more widely than the data it
exposes justifies, or a permission left broad because narrowing it would break someone._
## Defaults and configuration
Owner: product · last reviewed 2026-08
- **DEFAULT-1** — Defaults move toward the safe value, even when that breaks someone. _Violation: an
unsafe default kept for compatibility._
- **DEFAULT-2** — Prefer a single behaviour that fits most users of the product over a new setting. A
setting is the right answer when installations genuinely differ, not when a decision is hard to
make. Configuration growing over a product's lifecycle is normal, so this is a preference and not a
prohibition: _a new setting whose justification is "people will disagree" is worth questioning; one
whose justification is a real difference between installations is not._
## Interface
Owner: product & UX · last reviewed 2026-08
- **UI-1** — Icon-only controls carry their accessible name. _Violation: a button whose only content
is an icon and that has no tooltip or label supplying the name._
- **UI-2** — Actions revealed only on hover or focus are reachable on touch devices. _Violation: a
row action that exists only behind `:hover`._
- **UI-3** — Responsive layouts adapt to the space their container actually has, not to the viewport.
_Violation: a fixed column count that only changes at global breakpoints, inside something that can
sit in a sidebar, a modal or a split pane._
- **UI-4** — In the contexts that use the taskbar — tickets, and the knowledge base in the new
desktop view — work that auto-saves and has to survive the user switching to another context for a
moment lives in a taskbar tab. The taskbar is not the container for everything; it is the container
for resumable work in those contexts. _Violation: putting auto-saving, resumable work in a modal or
a flyout that cannot be left open._
- **UI-5** — An action that deletes, removes or discards something the user cannot get back is
confirmed before it happens, through the shared confirmation dialog. _Violation: a delete that goes
straight through, or a bespoke confirm dialog next to the shared one._
## Deployment
Owner: development · last reviewed 2026-08
- **DEPLOY-1** — What the application is made of is decided at **build time**. A running instance
never mutates its own application files, and a container least of all. _Violation: any feature that
writes into the application directory at runtime and expects it to survive — installing a package,
dropping in a custom file override, generating assets in place._
Worth stating rather than assuming, because the codebase already contradicts it:
`app/models/package.rb` writes to `Rails.root` on install and uninstall, and reads
`auto_install/`. That mechanism predates containers being a supported deployment.
This principle rules the runtime write **out**. It says nothing about what replaces it — which of
an image layer, a mount or a documented build step is the supported answer is **unsettled**, so
treat a story that proposes one of them as touching an open question rather than as aligned.
## Language and localisation
Owner: product · last reviewed 2026-08
- **LANG-1** — Source strings are complete, meaningful sentences in sentence case. _Violation: a
single standalone word as a translatable string — translators have no context to disambiguate it._
- **LANG-2** — An existing string is reused rather than a near-duplicate introduced. _Violation: a
new string that differs from an existing one only in punctuation or word order; every distinct
string is work in every supported language._
- **LANG-3** — Translations come from translations.zammad.org. _Violation: editing `i18n/*.po`
directly, or proposing that as the fix for a wording problem._
- **LANG-4** — Locale codes follow the standard, even when correcting one is a breaking change.
_Violation: keeping a wrong locale code because changing it would affect existing installations._
## Honesty about what the system does
Owner: product · last reviewed 2026-08
- **HONEST-1** — Labels describe what the system actually did, not the most impressive reading of it.
In particular, AI is not claimed for mechanical behaviour: a similarity search is presented as
knowledge, not as AI understanding. _Violation: wording that implies reasoning where there is
retrieval, or that credits AI for a step it only fed into._
This is the one principle in the file that cost a whole story to learn — `zammad/zammad#6323`
renamed "Suggested by AI" to "Suggested knowledge" because the label overstated a similarity
search. It is also the product-level form of the company's stated commitment to AI that is "safe,
transparent, and aligned with our values".
## Stack direction
Owner: development · last reviewed 2026-08
- **STACK-1** — New features target Vue 3 and GraphQL. _Violation: new behaviour built on REST and
the legacy CoffeeScript interface without a stated reason, such as extending existing legacy
behaviour._
---
## Unsettled
Questions this file cannot answer yet. A consumer that hits one of these **raises it as a question
for a human** — it must not produce a verdict, and it must not infer the answer from the shape of the
existing code.
- **ACCESS-2 (proposed)** — Should every new surface have to state which roles can see and use it,
including the granular-permission case? "Agents" would not be an acceptable answer.
- **UI-6 (proposed)** — Should new UI be required to compose existing design-system components and
tokens, with a new primitive treated as a decision rather than a detail?
- **UI-7 (proposed)** — Should a resumable view offer its own taskbar-behaviour-on-update setting,
stored independently per view? Seen once, in the story that introduced editing knowledge base
answers; unclear whether it generalises.
- **When is a new setting justified?** `DEFAULT-2` states the preference but not the test. There are
~300 settings today, so a usable criterion would have real leverage.
- **Are bulk or batch variants expected** for single-record actions, or added only on demand?
- **Where does deferred or scheduled state belong** in the interface? The same story grew a whole
scheduled visibility capability, which suggests there is no settled pattern for it yet.
- **How much of the legacy interface does a new feature have to reach**, and when is parity
explicitly not required?
- **How custom file overrides and packages are meant to work in a container**, given `DEPLOY-1`.
The principle says a runtime write is out; it does not say which of an image layer, a mount or a
build step is the answer. `zammad/zammad#6229` is blocked on exactly that choice.
- **Product direction beyond the above.** The company vision — democratising customer service,
transparency, open source, data protection — is real but not story-level: nothing in a story can
violate it. Whatever exists as actual direction is not written down anywhere in this repository
yet, and until it is, alignment with it is a question for product, never a finding.

View File

@@ -1,20 +0,0 @@
# Service Patterns
## Structure
Services live in `app/services/service/` and encapsulate business logic.
They are used e.g. by GraphQL resolvers and REST controllers.
- Pass all arguments via the execute class method, e.g. Service.execute(param: :value). It is the single entry point.
- Some services have with_current_user chainable method, e.g.g Service.with_current_user(user).execute(param: :value)
- Inherit from `Service::Base`
- Current user is supplied automatically and available as current_user instance method
- Services expecting current user should have requires_current_user! in their class body
- In specs, Service.execute class method should be stubbed when needed
- The service spec owns the behavior of everything the service decides — the
GraphQL spec around it only tests the GraphQL layer, see
`.dev/agent_docs/testing_services_and_graphql.md`
- Knowledge base services follow extra rules, see
`.dev/agent_docs/knowledge_base_patterns.md`
- Add attr_reader for all arguments passed to the initializer, so they are available in instance methods
- Service initializer should not have super() call, as Service::Base does not have an initializer

View File

@@ -1,462 +0,0 @@
# Zammad Story Rules
Single source of truth for when a story is ready to be planned, how it is sized, and what its plan
has to cover.
This file is **tool-neutral on purpose**. It says nothing about `gh`, GitHub issue types, board
columns, sub-issue mechanics or output format — those belong to whichever caller is using these
rules.
It is also **not a duplicate of the coding conventions**. Its sibling files in this directory are
the conventions; this file says which of them a plan has to account for, and it is the counterpart
of `code_review_rules.md` at the other end of the lifecycle: the same acceptance criteria that are
verified there are shaped and mapped here.
## Consumers
| Consumer | Lives in | Uses these rules for |
| -------------------- | ----------- | ------------------------------------------------------- |
| `refine-story` skill | skills repo | Checking and improving a story before it can be planned |
| `plan-story` skill | skills repo | Cutting a ready story into sub-issues and its plan |
When a rule changes, it changes **here** and every consumer picks it up. Do not copy rule text into
a consumer.
---
## Step 1 — Definition of ready
A story is plannable when someone can cut it into work without inventing the story first. Two
groups of checks; the mechanical ones are cheap and unambiguous, the judgement ones are not.
These checks are defined for **stories**. A bug has no user story and no acceptance criteria by
design, so M2 and M3 can never pass for one and gating on them would be meaningless. For anything
that is not a story, report the definition of ready as not applicable rather than as failed.
**Mechanical** — a caller may enforce these before anything else runs:
- **M1** The body is not a placeholder: it is non-empty, it is not `TBD`, `TDB` or `?` as its
entire content, and it carries **200 characters of prose or more** (fewer than 200 fails; exactly
200 passes). Prose is what remains after removing headings, code blocks, HTML comments, list
markers, emphasis and heading markup, and link and image syntax — link text counts, the URL does
not — and after collapsing every run of whitespace to a single space.
- **M2** A user story is present — a role, an action and a benefit, in whatever wording.
- **M3** An acceptance-criteria section is present. Heading wording varies
(`## Acceptance criteria`, `### Acceptance Criteria`); match tolerantly.
- **M4** At least two criteria, each an assertion rather than a question.
- **M5** No unresolved blocking item under a `Clarification needed` heading. Anything in that section
that is not a ticked task item (`- [x]` or `1. [x]`) counts as open. An open item is blocking
unless its text contains `non-blocking` or `blocks planning: no` — those two markers exactly, so
that two callers reading the same item reach the same verdict. Prose in the section with no list
items at all counts as one open blocking item, because a section that had nothing left to settle
would have been removed.
**Judgement** — decided by reading, never by pattern matching:
- **J1** Every criterion is verifiable: it states an observable outcome, not an intention.
- **J2** Scope is bounded. A criterion that delegates its own scope — "any other relevant surface",
"and wherever else this appears" — is not ready, because whoever plans it inherits an open search.
- **J3** Criteria do not contradict each other or the user story.
- **J4** Where the story is about UI, a design is linked and it is the current one (see Step 3).
- **J5** Open questions are stated as questions rather than implied by vague wording.
- **J6** No question anywhere on the story is left unanswered — and open questions belong under
`Clarification needed` in the body, not in a comment, so that answering one removes it.
### Verdicts
| Verdict | Meaning |
| ------------------- | -------------------------------------------------------------------- |
| **Ready** | plan it |
| **Ready with gaps** | plannable, but named gaps go to their owner first — ask before cutting |
| **Not ready** | do not plan. The list of failures _is_ the refinement to-do list |
A **Not ready** story is not a failure of the caller. Producing a confident plan from an unshaped
story is worse than producing nothing, because the plan then looks like a decision that was taken.
### Decisions taken outside the issue
A decision made in a meeting, a chat or a call is **not a decision** until it is written in the
story or in a comment on it. Everything downstream of this file reads the issue and its comments as
the record. If a criterion is known to be settled but nothing on the issue says so, that is an
unanswered question, not a settled one.
Where the body and a comment disagree, the **comment wins** — a body describing the
pre-discussion state is normal.
---
## Step 2 — Story size
> **A shared prerequisite is a dependency, not a scope boundary.**
This is the rule that matters most, and the one most often broken. Several capabilities that each
need the same base to exist are not one story. They are one base story plus several stories that
depend on it.
### The per-criterion test
This is a **closed checklist, not an impression**. For every acceptance criterion, write the single
sentence that connects it to the story's own outcome, then classify it:
| The sentence is… | Then |
| ------------------------------------------------ | ------------------------------------------ |
| "it also changes another view or object" | separate story — its scope left this story |
| "it needs the base to exist" | separate story, with a dependency |
| "the story's outcome is not achieved without it" | it stays |
| neither, or unclear | an open question, not a silent decision |
The rows are in **precedence order, and the first one that applies decides**. A criterion can
truthfully match more than one — something required for the outcome can still reach another view —
and without an order two callers classify the same story differently. The two separating tests
therefore run before the keeping one: a criterion stays only when neither of them fires.
Where the first two rows both apply, the result is still one separate story, and **both** facts get
recorded: the dependency, because the order of work depends on it, and the scope reach, because that
is the part product has to hear.
Every criterion gets exactly one classification. A criterion that cannot be classified is itself a
finding.
### Signals that a story is too big
Two or more firing is a conversation, not a footnote:
- More criteria than roughly five, or criteria carrying sub-bullets of their own.
- A criterion naming a capability the title does not imply.
- A criterion whose scope reaches outside the story's own object or view.
- More than one outstanding UX decision.
- More than one surface affected (desktop, mobile, legacy interface, public help site).
- A criterion needing its own backend surface — a new model, a new mutation, a new endpoint.
- More than one spike needed before the story can be planned at all.
- A blocked criterion holding unblocked criteria hostage.
That last one is the real cost, and it is easy to miss because nothing looks wrong. One unresolved
UX decision on one criterion delays every other criterion in the same story.
### Splitting is cheap
Issue types are `Epic` → `Story` → `Task`, with `Spike` a sibling of `Task` under the same story
(Step 6), and the Epic level is in active use. A story split into three stories under the same Epic
changes nothing about the hierarchy, the board or product's view of the work. What changes is that
each piece carries its own criteria and can ship on its own.
### What may not become a story
A **layer slice** can never be a story. "Add the service and the mutation" has no role, no benefit
and nothing observable — forcing it into story form produces a fake user story, which is worse than
a large one. Layer slices are tasks. Split stories along capability and surface seams only.
### Worked example — editing knowledge base answers
"Edit an answer of the knowledge base", one story, sixteen tasks, one very large merge request.
Its criteria classified by the test above:
| Proposed story | Criteria | Why separate |
| --------------------------------------- | ---------------------------------- | ---------------------------------- |
| Edit an answer | new editor, auto-save, own taskbar | this _is_ the outcome |
| Manage tags and attachments of an answer | tags, attachments | needs the base only |
| Link related tickets to an answer | links | needs the base only |
| Show live users on the edit tab | live users | needs the base only |
| Warn when an answer was edited meanwhile | concurrent-edit warning | needs the base only — blocked on UX |
| Configure taskbar behaviour on update | taskbar selector | also changes the _Add answer_ view |
| Schedule visibility changes | added a month after planning | own capability, own backend surface |
Seven stories where there was one. Only the first four words of the original title survive as the
base story. The concurrent-edit warning was blocked on a UX decision and, in the single-story
shape, blocked everything else with it.
The story also grew from ten to sixteen sub-issues after it was planned, because a new capability
was added as tasks rather than raised as a story. **The size check therefore runs again whenever a
breakdown is extended**, not only before the first cut.
---
## Step 3 — Alignment: criteria, use cases, design
Three lists, written at three different moments by three different people, and they drift. This step
is a **closed cross-check between lists that already exist**, in three directions — never a fresh
search for behaviour nobody asked for.
### Which design
Collect every design link from the body **and every comment**. The most recent link wins; an earlier
one is superseded, and a superseded link that is still in the body is itself a finding. If the
design cannot be read — no connector, no access — say so explicitly and mark every check in this
step as **not run**. Never infer a design's contents from the story's prose.
### Direction 1 — design → criteria
Something is drawn that no criterion asserts.
| Verdict | Meaning |
| ------------------ | ----------------------------------------------------------- |
| Asserted | a criterion covers it — cite the criterion |
| Missing a criterion | the design is right and the criteria are incomplete |
| Design overreach | the design went beyond what the story asked for |
The two gaps have different owners, and collapsing them is what makes a refinement unactionable.
"Missing a criterion" is work for whoever writes the story; "design overreach" is a scope
conversation.
### Direction 2 — criteria → design
Something is asserted that is not drawn.
| Verdict | Meaning |
| --------------- | -------------------------------------------------- |
| Drawn | the design covers it |
| Not drawn | UX work outstanding — name it |
| Not applicable | the criterion has no UI (backend, API, deprecation) |
"Not applicable" must be stated, not assumed. It is the verdict that gets skipped silently.
### Direction 3 — states the design does not contain
The most expensive failures are not disagreements between the lists — they are states nobody drew
and nobody wrote down, found during implementation or in review. Walk this list as a closed
checklist and give each row a verdict, including "not applicable":
- empty state
- loading state
- error and validation states
- permission-denied — and for which roles, including granular permissions
- long content, truncation, overflow
- zero vs one vs many
- disabled state
- unsaved changes when leaving
- concurrent edit by a second user
- narrow viewport and mobile
- dark mode
- keyboard operation and focus order
- accessible name for icon-only controls
- translation length, where a longer language breaks the layout
The UI rules that apply to each of these are in `code_review_rules.md` under **UI principles**, and
the condensed list is in `.dev/ai-agent-instructions.md`. Do not restate them; check against them.
### Direction 4 — use cases → criteria
Where the story states use cases separately from its criteria, run the mapping described in
`code_review_rules.md` under **Use-case cross-check**. It is defined there for review; the verdicts
and their meanings are identical here, one stage earlier, and a gap found here costs nothing to fix.
---
## Step 4 — Affected layers
Task breakdown is open-ended imagination, and open-ended imagination misses things. The structural
fix is a closed list.
Walk **every** row. Give each one a verdict, and **write the negative verdicts out** — "not
affected" with its reason is the whole point, because a row that is silently skipped was never
considered. Read the routed document only for rows that come out affected.
| Layer | Read |
| ----------------------------------------------------------- | --------------------------- |
| Data model, migration, seeds | `database_migrations.md` |
| Model behaviour, validations, legacy REST/notification concerns | `legacy_model_patterns.md` |
| Authorization — policies, permissions, granular roles | — |
| Service objects | `service_patterns.md` |
| GraphQL — types, mutations, queries, subscriptions | `graphql_patterns.md` |
| Form updater / Core Workflow | `frontend_patterns.md` |
| Vue frontend — name which apps (desktop, mobile, shared) | `frontend_patterns.md` |
| Addon layer | `frontend_addon_layer.md` |
| Legacy CoffeeScript interface and its REST controllers | — |
| Real time — subscriptions, taskbar, live users, concurrent edit | — |
| Search index | — |
| Background jobs and the scheduler | — |
| Settings, defaults, feature flags | — |
| Translations — new or changed source strings | `code_review_rules.md` |
| Tests — backend, frontend, system | `testing.md` |
| Documentation — admin docs, system requirements | — |
| Breaking changes | `BREAKING_CHANGES.md` |
Two rows are missed most often and are worth naming: **authorization**, because a feature works
perfectly while being visible to the wrong role, and **breaking changes**, because nothing in the
diff looks like a breaking change. A story that removes, renames, restricts or raises anything needs
a `BREAKING_CHANGES.md` entry, and that entry is work in its own right.
Where a row is affected but cannot yet be planned, it becomes a **spike** — a piece of work whose
output is an answer, not a change. What a spike issue contains is in Step 6. Never hide an
unresolved question inside an implementation task.
---
## Step 5 — Depth of the plan
The plan exists so that whoever implements — a person or an agent — does not repeat the research,
and so that the review afterwards has something concrete to check the result against. It is not a
pre-written diff.
Go to code level **only** when one of these is true:
1. **The obvious reference diverges** — the thing that looks right to copy is the wrong thing.
2. **There is a trap** — something that looks right and is not, or something that must explicitly
not be done, and why.
3. **An ordering or invariant constraint** that could not be inferred from reading the files.
4. **A decision was already taken**, with its reason, so nobody re-litigates it and the reviewer can
check the result against it.
Everything else is an anchor: the reference implementation to follow, the files and areas touched,
the specs to write, the open questions. Name the anchor and stop.
The test on any bullet is _which of the four reasons is this here for?_ If the answer is
"completeness", delete it. Argument lists, method signatures and file skeletons are derivable from
the reference implementation faster than they can be written down, and they are wrong by the time
anyone reads them.
Depth is **earned by risk**, never applied uniformly. A layer whose verdict in Step 4 came out clean
and boring gets an anchor and one line. A layer with a trap gets as much as the trap needs.
---
## Step 6 — What a task issue contains
Two audiences: a human who wants to know in thirty seconds what this is and why it exists, and
whoever implements it, starting from the body without redoing the research.
In this order:
1. **Lead paragraph** — what this piece of work is and where it sits: the reference implementation
it follows, the sibling it stays in sync with, the state of the code today. No heading.
2. **The plan** — grouped by file or area, at the depth Step 5 allows.
3. **Done means** — one sentence naming the observable state that makes this task finished. This is
what a reviewer judges against, and what an implementing agent stops at. It is not a copy of the
story's acceptance criteria.
4. **Open questions** — real ones only, each with a recommendation and, if it blocks, who decides.
Never a question without a proposed answer.
5. **Tests** — the spec files to add or extend and the cases they must cover, including edge cases
found during research. Say when coverage does not exist today.
6. **Dependencies** — what exactly is needed from which sibling. Only for siblings that already
exist; refer to not-yet-created ones in prose.
7. **Parent reference** — always the last line.
Sections 1, 3, 5 and 7 are mandatory. A small task is a lead paragraph, three bullets, a "Done
means" line, a "Tests" line and the parent reference.
### Rules
- **Nothing invented.** Every path, class, setting and pattern named in the plan was opened in the
checkout. If something could not be verified, say so in that bullet.
- **No copy of the story.** The task states what _it_ changes; the story is one click away.
- **No estimates, no assignees, no due dates.** That is the board's job.
- **No customer or personal data**, and no reference to a private support ticket beyond its number.
- One task is what one person can finish without waiting on a sibling, and what a reviewer can judge
on its own. It is deliberately **not** defined in terms of merge requests — how the work is
branched and merged is a separate decision that does not belong in a breakdown.
### What a spike issue contains
A **spike** is an issue type of its own, a sibling of `Task` under the same story — not a task with
a different name. Its output is an **answer**, so the task contract above does not fit it: there is
no plan of how to solve the thing, because what to solve is the open question. A spike carries:
1. **The question** — one sentence, answerable, naming the decision it unblocks.
2. **Why it cannot be answered now** — what was already read or tried, so the spike does not start
from zero.
3. **What an answer looks like** — the form the result has to take: a recommendation with its
reason, a measurement, a throwaway proof of concept. Name which one.
4. **Done means** — the answer is written down _in the spike_, in its body or as a comment. A spike
whose result exists only in someone's head is not finished, and a spike does not finish by
producing production code.
5. **What it blocks** — the sibling tasks or criteria waiting on the answer. A spike that blocks
nothing is research, and research is not part of a breakdown.
6. **Parent reference** — always the last line.
All six are mandatory; a spike is short enough that none of them is a burden. Its traceability runs
the other way round from a task's: a spike traces to the **question** — the criterion or the Step 4
layer verdict that could not be planned — not to a criterion it implements.
### Coverage before anything is created
- Every acceptance criterion is covered by at least one task, and every task traces to a criterion.
A task tracing to nothing is either invented work or a criterion nobody wrote down — both are
findings, not things to resolve silently. A criterion that is blocked by a spike is covered by
that spike for now, and the fact that it has no task yet is recorded, not left to be noticed.
- Every layer verdict from Step 4 is reflected: affected rows have a task, unaffected rows are
recorded as unaffected. An affected row that cannot be planned yet is covered by the **blocking
spike** instead — the spike names the row it holds, and the implementing task is created once the
spike is answered. A row covered by neither is the gap this check exists to catch.
- Decisions taken in the story's comments are reflected, and outrank the body.
- What the story leaves out stays out. Where the story is vague, that becomes an open question in
the affected task, not a silent decision.
---
## Step 7 — Writing back to the story
A caller may improve the story it is working on. Two sections behave differently, and the
distinction is not cosmetic.
**Acceptance criteria are a contract.** `code_review_rules.md` enumerates them verbatim and gives
each one a verdict, months later. Rewording a criterion silently changes what gets verified.
Criteria may only be added, changed or removed after an explicit confirmation from a human, one
change at a time, with the reason recorded.
A story may also carry a **`Solution outline`**: a few lines on which areas and layers are affected,
the shape of the approach, and what was ruled out. It is the only place the whole solution is visible
without opening every task. It names areas and decisions, never files, classes or methods — those
belong in the tasks, where they are checked against the checkout and can be corrected without
touching product's artefact.
**Everything else may be improved freely** — background information, technical notes, links, a scope
or out-of-scope section, a clarification section. Prefer adding a section over rewriting a
paragraph.
Mechanics that apply to any write-back:
- **Edit by section. Never regenerate the body.** Sections with nothing to add stay byte-identical.
- **Open questions live in the body, never in a comment.** They go under `Clarification needed`, as
task items, because the body is current state: answering a question removes its item, and the
answer becomes a criterion or background. A comment cannot do that — and comments are read
downstream as settled context, so a question posted there is later taken for a decision.
- **A body edit notifies nobody, but a comment is not automatic.** Post one when the change would
surprise somebody who read the story before: a criterion changed or removed, scope narrowed or
widened, a decision that reverses an earlier one. An addition that fills a gap needs none — the
body shows it and the edit history keeps it. Which of the two a given change is, is a judgement for
the caller; this file does not try to decide it.
- **Never delete product's words** to make room. If the existing wording is wrong, propose the
replacement; do not apply it silently.
- Splitting a story is a **product act**. Propose the split, never execute it.
---
## Alignment with product direction
Two different things get called this, and only one is checkable.
**Mechanically checkable, no extra source needed:**
- Does the design compose existing design-system components and tokens, or invent new ones? Whether
composing them is _required_ is unsettled (`.dev/agent_docs/product_principles.md#UI-6`), so a new
primitive is raised as a question for UX, never reported as a violation.
- Does a new source string near-duplicate an existing one? (`code_review_rules.md`, translatable
strings.)
- Does the story build new behaviour on REST and the legacy CoffeeScript interface? New features
target Vue 3 and GraphQL, but the principle keeps an exception, so this is a finding **only when
the story states no reason** — extending existing legacy behaviour is a reason, and a stated one
closes the check (`.dev/agent_docs/product_principles.md#STACK-1`).
- Does it need a pattern that does not exist in the codebase yet?
Two of these overlap a principle below. Where they do, the **principle's wording decides**,
exceptions included; the bullet is only the prompt to look.
**Checkable against `product_principles.md`**, in this directory: it states what the product holds to
be true, each principle as an assertion plus what it rules out, with stable IDs to cite
(`.dev/agent_docs/product_principles.md#UI-5`). Check against it; do not restate it here.
Its **Unsettled** section is as important as the rest. A story that touches one of those questions
gets a **question for a human**, never a verdict — and the distinction between "we decided against
it" and "we never decided" only exists because that section is written down. If the file is missing,
report product alignment as **not run**. Never reason about product direction from memory or from the
shape of the existing code.
---
## What none of this decides
- Which repository sub-issues are filed in, which board they land on, and in which column. That is
team configuration and belongs to the caller.
- How the work is branched and merged.
- Sizing in points or days. Nothing here produces an estimate.
- Whether a story should exist at all. That is product's call, upstream of every rule in this file.

View File

@@ -1,128 +0,0 @@
# Testing
## Strategy
- Test at the **lowest level** possible — unit over request over E2E
- Focus on the object under test, mock dependencies where it makes sense
- Only important user stories get E2E/system tests (Capybara + Selenium)
- Service specs cover behavior, GraphQL specs only the GraphQL layer —
see `.dev/agent_docs/testing_services_and_graphql.md`
## Backend (RSpec)
Tests live in `spec/` mirroring the `app/` structure.
Factories are in `spec/factories/`.
```bash
RAILS_ENV=test VITE_TEST_MODE=1 bundle exec rspec spec/path/to/file_spec.rb # Run file
RAILS_ENV=test VITE_TEST_MODE=1 bundle exec rspec spec/path/to/file_spec.rb:42 # Run specific example
```
Add `CI_SKIP_DB_RESET=1` to skip database reset between runs.
Add `CI_SKIP_ASSETS_PRECOMPILE=1` to skip asset compilation —
but omit it when frontend files have changed.
### RSpec meta attributes
- `authenticated_as:` — accepts `true` (admin), `false` (skip login),
a User, Symbol, or lambda
- `performs_jobs: true` — required to run ActiveJob jobs
outside of `spec/jobs/`
- `db_strategy: :reset` — reset database after examples that modify
the schema
### System tests (Capybara + Selenium)
Set `SELENIUM_BROWSER_HEADLESS=1` when running system specs — otherwise
Selenium opens a real, visible browser window.
```bash
SELENIUM_BROWSER_HEADLESS=1 RAILS_ENV=test VITE_TEST_MODE=1 bundle exec rspec spec/system/path/to/file_spec.rb
```
### Ports of a test run
The suite serves Capybara on `CAPYBARA_PORT` (default `3001`) and its websocket
server on `WS_PORT` (default `6043`) — next to, never on, the ports of the
development stack, so a run does not collide with a stack of the same checkout.
Set both to run two suites side by side; they still share the `test` database,
the Redis keys and the Elasticsearch indices, so those (`DATABASE_URL`,
`REDIS_URL`, `ES_INDEX`) need separating as well.
```bash
CAPYBARA_PORT=3101 WS_PORT=6143 bundle exec rspec spec/system/path/to/file_spec.rb
```
### Capybara form helpers (new frontend stack)
System tests for the Vue frontend use custom helpers to interact
with FormKit-based fields:
```ruby
find_input('Title').type('text')
find_select('Owner').select_option('Agent Name')
find_autocomplete('Customer').search_for_option(
customer.email, label: customer.fullname
)
find_editor('Text').type('content')
find_treeselect('Category').select_option('Parent::Child')
find_datepicker('Date').select_date(Date.tomorrow)
find_toggle('Boolean').toggle_on
```
Use `within_form` for stable multi-field interactions — it automatically
waits for form updater (Core Workflow) responses:
```ruby
within_form(form_updater_gql_number: 2) do
find_autocomplete('CC').search_for_options([email_1, email_2])
find_editor('Text').type(body)
end
```
For the new frontend system tests, use `wait_for_gql` to wait for
GraphQL responses and `wait_for_test_flag` to wait for frontend
state changes.
Full reference:
`doc/developer_manual/cookbook/how-to-test-with-rspec-and-capybara.md`
## Frontend (Vitest)
Test files use `.spec.ts` extension and are co-located with source files.
```bash
pnpm test --run app/frontend/path/to/file.spec.ts
```
Always pass `--run` and at least one file path. In an interactive terminal,
watch mode is the default and does not exit. Never put `--` between `pnpm test` and the arguments: pnpm
forwards it, Vitest then ignores everything after it and runs the complete
suite. `VITE_TEST_MODE` is not needed, Vitest enables the test mode itself.
Tests use **Testing Library** (on top of Vue Test Utils).
### Two levels of frontend tests
- **Component/unit tests** — co-located with source files,
test individual components or composables in isolation
- **Frontend integration tests** — live in `__tests__/` folders
inside page directories, render the full view with routing
GraphQL requests are mocked via the auto mocker. Activate it by
importing `#tests/graphql/builders/mocks.ts` or by importing a
`.mocks.ts` file next to a GraphQL operation.
Cypress is only used when pure Node.js testing is not possible
(e.g. the editor). See
`doc/developer_manual/cookbook/how-to-test-with-vitest-and-cypress.md`.
### Vitest utils
To handle some vue specific setup we use:
For component test -
`app/frontend/tests/support/components/renderComponent.ts`
For integration test -
`app/frontend/tests/support/components/visitView.ts`

View File

@@ -1,36 +0,0 @@
# Service and GraphQL Specs
How to split spec coverage between a service and the query, mutation or
subscription that calls it. For everything else about tests see
`.dev/agent_docs/testing.md`.
When a query, mutation or subscription delegates to a service, test each
layer once. Write the service spec first.
Service spec (`spec/services/service/...`) — everything the service
decides:
- what it writes, and what it leaves alone
- failures, as the raised exception, plus the rollback
- authorization it performs itself, and `requires_current_user!`
- every form its arguments accept (a record as well as an id or code)
GraphQL spec (`spec/graphql/gql/{queries,mutations,subscriptions}/...`) —
only what the GraphQL layer adds:
- schema-level rejections: missing or blank arguments, custom scalars
- declarative authorization: `requires_permission`,
`loads_pundit_method:`, and the shared example
`graphql responds with error if unauthenticated`
- how a service error reaches the client: user error with `message` and
`field`, or a top-level error
- the payload: which fields come back, in which locale
- fields the types resolve themselves (batch loaders, connections,
pagination) and their query-count tests
Do not repeat the service's matrix (roles, locales, permissions, tree
shapes) in the GraphQL spec. One loose example that an argument reaches
the service is enough. Name the service spec in a comment at the top.
Example: `spec/services/service/knowledge_base/category/create_spec.rb`
and `spec/graphql/gql/mutations/knowledge_base/category/add_spec.rb`.

View File

@@ -1,73 +0,0 @@
---
name: code-reviewer
description: Reviews uncommitted changes for convention violations, missing tests, unintended side effects, and implementation correctness. Invoke this after the Test phase, before Commit.
---
# Code Reviewer
You are a code reviewer for the Zammad project.
Your job is to review the changes the main agent has just made — before they
are committed — and report findings.
## Process
1. Determine what to review.
- **Default** — invoked from the development workflow, after Test and before Commit —
run `git diff HEAD` for all uncommitted changes, staged and unstaged.
- **If the caller supplied a scope, use that instead.** The `code-review` skill is
invoked by a person outside the workflow and may pass a branch-wide scope
(`merge-base(develop, HEAD)` → working tree), where `git diff HEAD` would be empty.
Also check for untracked files, which appear in no diff. With no caller-supplied
scope, run `git ls-files --others --exclude-standard` repository-wide. With a
supplied scope, constrain that command to the same pathspecs (or the paths covered
by the supplied diff) using `git ls-files --others --exclude-standard -- <paths>`.
Do not include unrelated untracked files outside that scope.
2. Identify which areas of the codebase the changes touch (CoffeeScript
frontend, Vue 3 frontend, REST backend, GraphQL, services, migrations, etc.).
3. Read `.dev/agent_docs/code_review_rules.md`. It routes you to the applicable
`.dev/agent_docs/` reference docs for those areas — read only the ones that apply,
not all of them.
4. If the work relates to a GitHub issue, read it via `mcp__github__issue_read` to
understand the original requirements and acceptance criteria.
5. Review the diff and report findings.
## What to check
`.dev/agent_docs/code_review_rules.md` is the authority: it defines what qualifies as a
finding, what is out of scope, how to classify findings, and how to write them.
Apply it rather than your own judgement about what matters, and do not restate it
here.
Three things from it deserve emphasis in this position specifically:
- **The flagging threshold.** You may be reviewing work in progress, where scaffolding
and half-finished edges are expected. Report what meets the bar and nothing else.
If there are no findings, say so explicitly. Do not invent issues to fill the review.
- **You are not the author.** You run in a fresh context deliberately: you see the diff
and the issue, not the reasoning that produced the code. Judge what is there against
the conventions and the issue, and do not reconstruct or assume the author's intent —
the rules file treats that as disqualifying for a finding either way.
- **Leftover artifacts.** Debug code, `console.log`, `binding.pry`, commented-out
blocks and stray TODOs survive into agent diffs more often than into human ones.
This is the last checkpoint before they get committed.
## Output
Report findings as a structured list, grouped by severity in the order **P0**, **P1**,
then **P2**. Label each finding with the category from the taxonomy in the rules file.
For each finding include:
- File path and line number (if applicable)
- A clear description of the issue
- A concrete suggestion how to fix it
Anything at **P0** blocks the commit.
## Important
- You are READ-ONLY. Do not modify any files. Do not commit. Do not push.
If a fix is obvious, describe it — do not apply it.
- Be specific and actionable. Do not give vague feedback like "this could be better".
- Verify your claims by reading the actual code before reporting.

View File

@@ -1,181 +0,0 @@
# AI Agent Instructions
This file provides guidance on how to work with code in the Zammad repository.
## Project Overview
Zammad is an open-source helpdesk/support platform. The backend is **Ruby on Rails**.
There are two frontend stacks: the **current production frontend** is CoffeeScript
(served via the Rails asset pipeline), and the **new frontend** is **Vue 3 + TypeScript**
served via **Vite** (managed by pnpm), which is being built alongside it. **PostgreSQL** is
used for persistence, **Redis** for ActionCable/GraphQL subscriptions, and **GraphQL** is the
API layer between the new Vue apps and the backend.
## Architecture
```text
CoffeeScript frontend ──→ REST controllers ──→ Rails backend ──→ PostgreSQL
↑
Vue 3 frontend (desktop/mobile) ──→ GraphQL API ────┘
```
New features target the Vue 3 + GraphQL stack.
The CoffeeScript frontend uses REST controllers.
## Key Directories (non-standard)
- `app/services/service/` — Service objects encapsulating business logic
- `app/graphql/gql/` — GraphQL schema, types, mutations, subscriptions
- `app/policies/` — Authorization policies (Pundit)
- `app/frontend/apps/desktop/` — Vue 3 desktop app
- `app/frontend/apps/mobile/` — Vue 3 mobile app
- `app/frontend/shared/` — Shared Vue components, composables, stores, GraphQL types, form system
- `app/assets/javascripts/` — CoffeeScript frontend (Spine + jQuery, legacy, still actively maintained)
- `lib/` — Library code, prefer minimal Rails coupling
## General Guidelines
- All new files except Markdown files (`*.md`) must include the Zammad copyright header.
- Never edit translation files (`i18n/*.po`) directly —
translations are managed via translations.zammad.org.
### Code Comments
Comment only what the code cannot say itself: a non-obvious _why_, a
workaround with its reason, a spec/protocol quirk, or a deliberate
deviation from a pattern. Otherwise add no comment — clear naming and
small methods are preferred over explanation.
- One or two short lines, in the style of the surrounding file.
- Never restate what the next line does, summarise a block, label
sections (`# --- setup ---`), or narrate a change (`# new`, `# was …`,
`# fixed`).
- Keep out reasoning that belongs in the commit message, the issue or
the MR description.
- Never reference an issue from an internal repository (`coordination-*`) —
this repository is publicly mirrored. Reference a public `zammad/zammad`
issue, or state the reason without a link.
- Never mark a fix for a security vulnerability as such — this repository
is publicly mirrored, and the comment points at the vulnerable code in
installations that are not updated yet.
### UI Principles
- Icon-only buttons need `v-tooltip` supplying the accessible name.
- Hover/focus-only visible actions need to display on touch fallback
- Responsive grids should adapt to their container
- Translatable strings: use complete, meaningful source strings,
sentence case for English source strings, and check for existing
near-duplicate strings before adding new ones.
- Red marks the point of no return: `danger` and `remove` stay only on
controls that act immediately. A trigger that only opens a confirmation
dialog or a confirming flyout is not red: a button becomes `tertiary`
with an icon naming the action (`trash3` for delete and discard,
`arrow-counterclockwise` for reset), a menu item carries no variant, a
`remove` icon button keeps its icon and takes the variant UX names per
place. The decisive button in the dialog or flyout is `danger`.
## Essential Commands
### Backend
```bash
RAILS_ENV=test VITE_TEST_MODE=1 bundle exec rspec spec/path/to/file_spec.rb # Run specific RSpec test
bundle exec rubocop --autocorrect app/path/to/file.rb # Lint specific Ruby file(s)
```
### Frontend
Always use pnpm for frontend and cross-stack commands.
```bash
pnpm test --run app/frontend/path/to/file.spec.ts # Run specific Vitest test (never add `--`)
pnpm lint # Run all linters
pnpm generate-graphql-api # Regenerate GraphQL types after schema changes
pnpm generate-setting-types # Regenerate Config types after setting changes
```
### Development stack
A browser-clickable instance for this checkout. Start it via `bin/dev` rather
than `forego` directly, and wait for it to answer before using it.
```bash
RAILS_ENV=development bundle exec rake db:create zammad:bootstrap:reset # once per checkout
bin/dev # does not exit; run it in the background
```
It serves on `localhost:3000`, with the websocket server on `6042` and Vite on
`3036`. Export `ZAMMAD_RAILS_PORT`, `ZAMMAD_WEBSOCKET_PORT` and
`VITE_RUBY_PORT` before `bin/dev` to move them — the whole stack has to agree
on them, so export, do not set them per process.
`zammad:bootstrap:reset` migrates, seeds and runs the auto wizard, which creates
`admin@example.com` / `test`. Plain `zammad:db:init` skips the wizard and leaves
the browser on the getting-started screen. Never run `zammad:bootstrap:init` or
`zammad:setup:db_config`: they overwrite `config/database.yml` and prompt on
stdin, which hangs a non-interactive session.
The test suite uses its own ports (`3001` and `6043`) and its own database, so
it can run while the stack is up. Several checkouts, however, share the
development database and Redis — only one stack per machine unless
`DATABASE_URL` and `REDIS_URL` say otherwise.
## Development Lifecycle
Issues are tracked on **GitHub**, code is developed on a self-hosted **GitLab**.
GitHub hosts a read-only mirror. The agent uses the **GitHub MCP server** for
issues and **`glab` CLI** for GitLab operations.
Stories are refined and broken down into task issues before this lifecycle
starts (`/refine-story` → `/plan-story`). Bugs skip that and enter it directly.
Lifecycle: Understand → Research → Plan → Branch (`/prepare-issue-branch`) →
Implement → Test → Review → Commit → MR (`/create-mr`) → Cherry-pick
(`/cherry-pick-to-stable`)
See `.dev/agent_docs/development_workflow.md` for the full lifecycle and
workflow rules.
## Agent Reference Docs
You MUST read the relevant file(s) below before responding when working on that area — do NOT read them all upfront:
- `.dev/agent_docs/graphql_patterns.md` — How to add/modify GraphQL types,
mutations, queries, and subscriptions
- `.dev/agent_docs/frontend_patterns.md` — Vue component conventions,
composables, routing, and the form system
- `.dev/agent_docs/frontend_addon_layer.md` — How an addon extends the Vue
frontend via the build-time weave (Vapor-safe, multi-addon)
- `.dev/agent_docs/legacy_model_patterns.md` — Concerns needed by new models
used in the legacy stack: REST assets (one-to-one relations only) and
client notifications
- `.dev/agent_docs/testing.md` — How to write and run backend and frontend tests
- `.dev/agent_docs/testing_services_and_graphql.md` — How to split spec
coverage between a service and the query, mutation or subscription that
calls it
- `.dev/agent_docs/service_patterns.md` — Service object conventions and structure
- `.dev/agent_docs/knowledge_base_patterns.md` — Knowledge base rules for the
new stack (Vue/GraphQL/services) only: the single active knowledge base and
the deliberate exceptions to it
- `.dev/agent_docs/database_migrations.md` — How to write migrations and work
with seeds
## CodeGraph
In repositories indexed by CodeGraph (a `.codegraph/` directory exists at the repo
root), reach for it before grep/find or reading files when you need to understand
or locate code:
- **MCP tool** (when available): `codegraph_explore` answers most code questions
in one call — the relevant symbols' verbatim source plus the call paths between
them, including dynamic-dispatch hops grep can't follow. Name a file or symbol
in the query to read its current line-numbered source. If it's listed but
deferred, load it by name via tool search.
- **Shell** (always works): `pnpm exec codegraph explore "<symbol names or question>"`
prints the same output using the repository-pinned CodeGraph version.
If there is no `.codegraph/` directory, skip CodeGraph entirely — indexing is the
user's decision. If they ask to initialize it, run `pnpm codegraph:init`. Developer
setup and removal are documented in
`doc/developer_manual/development_environment/codegraph-setup.md`.

View File

@@ -20,14 +20,7 @@ export default defineConfig({
downloadsFolder: '.dev/cypress/downloads',
screenshotsFolder: '.dev/cypress/screenshots',
videoCompression: false,
// Cypress 16 defaults to `0`, which types faster than the rich text editor can
// process input events.
keystrokeDelay: 10,
// Cypress 16 defaults to `modern`, which relies on `Element.checkVisibility()`
// and therefore reports elements moved out of the viewport via `transform` as
// visible.
visibilityStrategy: 'legacy',
expose: {
env: {
CY_CI: isCYCI,
pluginVisualRegressionDiffConfig: {
threshold: 0.02,

View File

@@ -1,11 +1,14 @@
{
"private": true,
"type": "module",
"pnpm": {
"neverBuiltDependencies": []
},
"devDependencies": {
"@frsource/cypress-plugin-visual-regression-diff": "^4.2.1",
"@testing-library/cypress": "^10.1.3",
"cypress": "^16.1.1",
"cypress-real-events": "^1.15.1"
"@frsource/cypress-plugin-visual-regression-diff": "^3.3.10",
"@testing-library/cypress": "^10.0.3",
"cypress": "^13.17.0",
"cypress-real-events": "^1.14.0"
},
"imports": {
"#tests/*": "../../app/frontend/tests/*",

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
---
allowBuilds:
cypress: true
sharp: true
# Restore old default behavior like in pnpm v10.
verifyDepsBeforeRun: false

View File

@@ -29,59 +29,46 @@ Cypress.Commands.add('mount', mount)
* pastePayload: {hello: 'yolo'},
* });
*/
Cypress.Commands.add('paste', { prevSubject: true }, function onPaste(subject, pasteOptions) {
const { pastePayload = '', pasteType = 'text', files = [] } = pasteOptions
const data = pasteType === 'application/json' ? JSON.stringify(pastePayload) : pastePayload
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
const clipboardData = new DataTransfer()
clipboardData.setData(pasteType, data)
files.forEach((file) => {
clipboardData.items.add(file)
})
// https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
const pasteEvent = new ClipboardEvent('paste', {
bubbles: true,
cancelable: true,
dataType: pasteType,
data,
clipboardData,
})
subject[0].dispatchEvent(pasteEvent)
return subject
})
Cypress.Commands.add('selectText', { prevSubject: true }, (subject, direction, size) => {
return cy
.wrap(subject)
.realPress([
'Shift',
...new Array(size).fill(direction === 'right' ? 'ArrowRight' : 'ArrowLeft'),
])
})
// :TODO - remove this helper if we decide in the long run to not reintroduce the uniqueID extension from tiptap
const normalizeHTML = (html) =>
html
// strip node ids from unique ids
.replaceAll(/\sdata-id="[^"]*"/g, '')
Cypress.Commands.add(
'shouldContainNormalizedHtml',
{ prevSubject: 'element' },
(subject, expected) => {
const actual = normalizeHTML(subject[0].innerHTML)
const wanted = normalizeHTML(expected)
expect(actual).to.contain(wanted)
'paste',
{ prevSubject: true },
function onPaste(subject, pasteOptions) {
const { pastePayload = '', pasteType = 'text', files = [] } = pasteOptions
const data =
pasteType === 'application/json'
? JSON.stringify(pastePayload)
: pastePayload
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
const clipboardData = new DataTransfer()
clipboardData.setData(pasteType, data)
files.forEach((file) => {
clipboardData.items.add(file)
})
// https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
const pasteEvent = new ClipboardEvent('paste', {
bubbles: true,
cancelable: true,
dataType: pasteType,
data,
clipboardData,
})
subject[0].dispatchEvent(pasteEvent)
return subject
},
)
Cypress.Commands.add(
'shouldHaveNormalizedHtml',
{ prevSubject: 'element' },
(subject, expected) => {
const actual = normalizeHTML(subject[0].innerHTML)
const wanted = normalizeHTML(expected)
expect(actual).to.equal(wanted)
'selectText',
{ prevSubject: true },
(subject, direction, size) => {
return cy
.wrap(subject)
.realPress([
'Shift',
...new Array(size).fill(
direction === 'right' ? 'ArrowRight' : 'ArrowLeft',
),
])
},
)

View File

@@ -16,7 +16,7 @@ globalThis.process.env = {
Cypress.Screenshot.defaults({ capture: 'viewport' })
if (Cypress.expose('CY_CI')) {
if (Cypress.env('CY_CI')) {
Cypress.config('defaultCommandTimeout', 20000)
}

View File

@@ -71,11 +71,6 @@ declare global {
): Chainable<Subject>
mount: (typeof import('cypress/vue'))['mount']
}
interface Chainable<Subject> {
shouldContainNormalizedHtml(expected: string): Chainable<Subject>
shouldHaveNormalizedHtml(expected: string): Chainable<Subject>
}
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
import { merge } from 'lodash-es'
import { initializeAppName } from '#shared/composables/useAppName.ts'
import initializeStore from '#shared/stores/index.ts'
import initializeGlobalComponents from '#shared/initializer/globalComponents.ts'
@@ -28,35 +27,12 @@ import createCache from '#shared/server/apollo/cache.ts'
import { createMockClient } from 'mock-apollo-client'
import { provideApolloClient } from '@vue/apollo-composable'
import { useSessionStore } from '#shared/stores/session.ts'
import { useApplicationStore } from '#shared/stores/application.ts'
const router = createRouter({
history: createMemoryHistory('/'),
routes: [{ path: '/', component: { template: '<div />' } }],
})
const mockPermissions = (permissions: string[]) => {
const session = useSessionStore()
if (!session.user) {
session.user = {
id: '123',
internalId: 1,
objectAttributeValues: [],
}
}
session.user!.permissions = { names: permissions }
}
const mockApplicationConfig = (config: Record<string, unknown>) => {
const app = useApplicationStore()
Object.entries(config).forEach(([key, value]) => {
app.config[key] = value
})
}
export const mountComponent: typeof mount = (
component: any,
options: any,
@@ -75,10 +51,7 @@ export const mountComponent: typeof mount = (
plugins.push(initializeMobileVisuals)
plugins.push((app: App) => router.install(app))
return cy.mount(component, merge({ global: { plugins } }, options)).then(() => {
if (options?.config) mockApplicationConfig(options.config)
if (options?.permissions) mockPermissions(options.permissions)
})
return cy.mount(component, merge({ global: { plugins } }, options))
}
export const mockApolloClient = () => {

View File

@@ -2,35 +2,28 @@
## Updating Image Snapshots
This command only _updates_ snapshots. Cypress will run only tests with `*-visual.cy` in their name, and will not fail,
if snapshot is not correct, but will generate a new snapshot instead.
This command only _updates_ snapshots. Cypress will run only tests with `*-visual.cy` in their name, and will not fail, if snapshot is not correct, but will generate a new snapshot instead.
```sh
# from the root directory
pnpm cypress:snapshots
$ pnpm cypress:snapshots
```
Please, make sure snapshot is actually correct before pushing your changes.
## Running Tests
Although you may run visual tests using `pnpm test:ci:ct` command, this will execute them in your current development
environment. However, stored image snapshots were generated in Docker containers in order to achieve consistent
environment every time. Due to multiple factors (i.e. screen density, font resolution, browser version, etc), your
mileage may vary and you may receive false negatives.
Although you may run visual tests using `pnpm test:ci:ct` command, this will execute them in your current development environment. However, stored image snapshots were generated in Docker containers in order to achieve consistent environment every time. Due to multiple factors (i.e. screen density, font resolution, browser version, etc), your mileage may vary and you may receive false negatives.
It is therefore advisable to run the visual regression tests in the same environment where the image snapshots are
normally updated. You can do this by setting an environment variable and running the snapshots command:
It is therefore advisable to run the visual regression tests in the same environment where the image snapshots are normally updated. You can do this by setting an environment variable and running the snapshots command:
```sh
# from the root directory
CYPRESS_UPDATE_SNAPSHOTS=false pnpm cypress:snapshots
$ CYPRESS_UPDATE_SNAPSHOTS=false pnpm cypress:snapshots
```
## Multiple Architecture (AMD64 vs ARM)
Cypress doesn't work inside amd64 image, if it's running on ARM processor, even under `--platform=linux/amd64`. So we
have two compose files, and based on user processor we run one or the other.
Cypress doesn't work inside amd64 image, if it's running on ARM processor, even under `--platform=linux/amd64`. So we have two compose files, and based on user processor we run one or the other.
ARM compose file has a custom cache folder, so it doesn't conflict with Mac's usual `~/Library/Cache` folder. It's
meant to store cache inside the project folder, but Linux cache can be theoretically shared.
ARM compose file has a custom cache folder, so it doesn't conflict with Mac's usual `~/Library/Cache` folder. It's meant to store cache inside the project folder, but Linux cache can be theoretically shared.

View File

@@ -1,4 +1,5 @@
---
version: '3.6'
volumes:
visual-regression-dependencies-amd64:

View File

@@ -1,4 +1,5 @@
---
version: '3.6'
volumes:
visual-regression-dependencies-arm:

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env bash
corepack enable pnpm
pnpm install --frozen-lockfile --ignore-scripts
pnpm cypress:install
pnpm test:ci:ct --expose pluginVisualRegressionUpdateImages=$CYPRESS_UPDATE_SNAPSHOTS --spec '../../**/*-visuals.cy.*'
pnpm cypress:install --frozen-lockfile
pnpm test:ci:ct --env pluginVisualRegressionUpdateImages=$CYPRESS_UPDATE_SNAPSHOTS --spec '../../**/*-visuals.cy.*'

51
.dev/hygen/.hygen.js Normal file
View File

@@ -0,0 +1,51 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
const config = require('./lib.config.js')
module.exports = {
helpers: {
componentLibrary: (directoryScope, path = true) => {
if (directoryScope === 'Desktop') return path ? 'apps/desktop' : 'desktop'
if (directoryScope === 'Mobile') return path ? 'apps/mobile' : 'mobile'
if (directoryScope === 'Shared') return 'shared'
throw new Error('Directory scope not found')
},
getPath(type, options) {
switch (type) {
case 'genericComponent':
return `../../app/frontend/${this.componentLibrary(options.directoryScope)}/components/${options.suffix}`
case 'composable':
return `../../app/frontend/${this.componentLibrary(options.directoryScope)}/composables/${options.suffix}`
case 'store':
return `../../app/frontend/${this.componentLibrary(options.directoryScope)}/stores/${options.suffix}`
default:
return type
}
},
zammadCopyright: () => {
return `Copyright (C) 2012-${new Date().getFullYear()} Zammad Foundation, https://zammad-foundation.org/`
},
usePrefix(name, type = 'use') {
if (type === 'use') {
const nameWithGenericPrefix = name.replace(
new RegExp(`${config.convention.vue.use}`, 'i'),
'',
)
return this.changeCase.camel(
`${config.convention.vue.use}${this.changeCase.pascal(nameWithGenericPrefix)}`,
)
}
if (type === 'generic') {
const nameWithGenericPrefix = name.replace(
new RegExp(`${config.generic.prefix}`, 'i'),
'',
)
return this.changeCase.pascal(
`${config.generic.prefix}${this.changeCase.pascal(nameWithGenericPrefix)}`,
)
}
},
},
}

View File

@@ -0,0 +1,10 @@
---
to: <%- h.getPath(path) %>
---
<!-- <%= h.zammadCopyright() %> -->
<script setup lang="ts"></script>
<template>
<div>Hello <%= h.convertCase.pascal(componentName) %></div>
</template>

View File

@@ -0,0 +1,16 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module.exports = [
{
type: 'input',
name: 'path',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'Path from directory scope (desktop/mobile/shared)?',
},
{
type: 'input',
name: 'name',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'Component name?',
},
]

View File

@@ -0,0 +1,8 @@
---
to: "<%= h.getPath('composable', {directoryScope: directoryScope, suffix: `${h.usePrefix(composableName, 'use')}.ts`}) %>"
---
// <%= h.zammadCopyright() %>
export const <%= h.usePrefix(composableName, 'use') %> = () => {
return {}
}

View File

@@ -0,0 +1,13 @@
---
to: "<%= h.getPath('composable', {directoryScope: directoryScope, suffix: `__tests__/${h.usePrefix(composableName, 'use')}.spec.ts`}) %>"
---
// <%= h.zammadCopyright() %>
import { <%= h.usePrefix(composableName, 'use') %> } from '#<%= h.componentLibrary(directoryScope, false) %>/composables/<%= h.usePrefix(composableName, 'use') %>.ts'
describe('<%= h.usePrefix(composableName, 'use') %>', () => {
it.todo('test <%= h.usePrefix(composableName, 'use') %>', () => {
const composable = <%= h.usePrefix(composableName, 'use') %>()
expect(composable)
})
})

View File

@@ -0,0 +1,21 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module.exports = [
{
type: 'select',
name: 'directoryScope',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'Where should the component be created?',
choices: [
{ directoryScope: 'desktop', message: 'Desktop' },
{ directoryScope: 'mobile', message: 'Mobile' },
{ directoryScope: 'shared', message: 'Shared' },
],
},
{
type: 'input',
name: 'composableName',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'Composable name?',
},
]

View File

@@ -0,0 +1,16 @@
---
to: "<%= h.getPath('genericComponent', {directoryScope: directoryScope, suffix: `${h.usePrefix(componentName, 'generic')}/${h.usePrefix(componentName, 'generic')}.vue`}) %>"
---
<!-- <%= h.zammadCopyright() %> -->
<script setup lang="ts">
interface Props {
name: string
}
defineProps<Props>()
</script>
<template>
<div>Hello <%= h.usePrefix(componentName, 'generic') %></div>
</template>

View File

@@ -0,0 +1,14 @@
---
to: "<%= h.getPath('genericComponent', {directoryScope: directoryScope, suffix: h.usePrefix(componentName, 'generic') + '/__test__/' + h.usePrefix(componentName, 'generic') + '.spec.ts'}) %>"
---
// <%= h.zammadCopyright() %>
import { renderComponent } from '#tests/support/components/index.ts'
import <%= h.usePrefix(componentName, 'generic') %> from '#<%= h.componentLibrary(directoryScope, false) %>/components/<%= h.usePrefix(componentName, 'generic') %>/<%= h.usePrefix(componentName, 'generic') %>.vue'
describe('<%= h.usePrefix(componentName, 'generic') %>', () => {
it.todo('renders <%= h.usePrefix(componentName, 'generic') %>', () => {
renderComponent(<%= h.usePrefix(componentName, 'generic') %>, {})
})
})

View File

@@ -0,0 +1,8 @@
---
to: "<%= withComposable ? h.getPath('genericComponent', {directoryScope: directoryScope, suffix: `${h.usePrefix(componentName, 'generic')}/${h.usePrefix(componentName, 'use')}.ts`}) : null %>"
---
// <%= h.zammadCopyright() %>
export const <%= h.usePrefix(componentName, 'use') %> = () => {
return {}
}

View File

@@ -0,0 +1,13 @@
---
to: "<%= withTypeFile ? h.getPath('genericComponent', {directoryScope, suffix: `${h.usePrefix(componentName, 'generic')}/__test__/${h.usePrefix(componentName, 'use')}.spec.ts`}) : null %>"
---
// <%= h.zammadCopyright() %>
import { <%= h.usePrefix(componentName, 'use') %> } from '#<%= h.componentLibrary(directoryScope, false) %>/components/<%= h.usePrefix(componentName, 'generic') %>/<%= h.usePrefix(componentName, 'use') %>.ts'
describe('<%= h.usePrefix(componentName, 'use') %>', () => {
it.todo('test <%= h.usePrefix(componentName, 'use') %>', () => {
const composable = <%= h.usePrefix(componentName, 'use') %>()
expect(composable)
})
})

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module.exports = [
{
type: 'select',
name: 'directoryScope',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'Where should the component be created?',
choices: [
{ directoryScope: 'desktop', message: 'Desktop' },
{ directoryScope: 'mobile', message: 'Mobile' },
{ directoryScope: 'shared', message: 'Shared' },
],
},
{
type: 'input',
name: 'componentName',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'Component name?',
},
{
type: 'confirm',
name: 'withComposable',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'Should generate a composable?',
initial: true,
},
{
type: 'confirm',
name: 'withTypeFile',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'Should generate a type file?',
initial: true,
},
]

View File

@@ -0,0 +1,9 @@
---
to: "<%= withTypeFile ? h.getPath('genericComponent', {directoryScope, suffix: `${h.usePrefix(componentName, 'generic')}/types.ts`}) : null %>"
---
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
export interface Dummy {
// Add your interface here
[key: string]: unknown
}

View File

@@ -0,0 +1,21 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module.exports = [
{
type: 'select',
name: 'directoryScope',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'Where should the store be created?',
choices: [
{ directoryScope: 'desktop', message: 'Desktop' },
{ directoryScope: 'mobile', message: 'Mobile' },
{ directoryScope: 'shared', message: 'Shared' },
],
},
{
type: 'input',
name: 'storeName',
// eslint-disable-next-line zammad/zammad-detect-translatable-string
message: 'How is the store called?',
},
]

View File

@@ -0,0 +1,14 @@
---
to: "<%= h.getPath('store', { directoryScope: directoryScope, suffix:`/${storeName}.ts`}) %>"
---
// <%= h.zammadCopyright() %>
import { acceptHMRUpdate, defineStore } from 'pinia'
export const <%= h.usePrefix(storeName) %> = defineStore('<%= storeName %>', () => {
return {}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(<%= h.usePrefix(storeName) %>, import.meta.hot))
}

View File

@@ -0,0 +1,17 @@
---
to: "<%= h.getPath('store', { directoryScope: directoryScope ,suffix:`__tests__/${storeName}.spec.ts`})%>"
---
// <%= h.zammadCopyright() %>
import { createPinia, setActivePinia, storeToRefs } from 'pinia'
describe('<%= h.usePrefix(storeName) %>', () => {
beforeEach(()=> {
setActivePinia(createPinia())
})
it.todo('should be tested', ()=> {
const store = <%= h.usePrefix(storeName) %>()
expect(store).toBeDefined()
})
})

13
.dev/hygen/lib.config.js Normal file
View File

@@ -0,0 +1,13 @@
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module.exports = {
generic: {
name: 'Zammad Generic Component',
prefix: 'Common',
},
convention: {
vue: {
use: 'use',
},
},
}

11
.dev/hygen/package.json Normal file
View File

@@ -0,0 +1,11 @@
{
"private": true,
"version": "1.0.0",
"description": "DX packages",
"pnpm": {
"neverBuiltDependencies": []
},
"dependencies": {
"hygen": "^6.2.11"
}
}

709
.dev/hygen/pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,709 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
hygen:
specifier: ^6.2.11
version: 6.2.11
packages:
'@types/node@17.0.45':
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
async@3.2.5:
resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
camel-case@3.0.0:
resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
change-case@3.1.0:
resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==}
cli-cursor@3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
cli-spinners@2.9.2:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
clone@1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
constant-case@2.0.0:
resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
debug@4.3.6:
resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
defaults@1.0.4:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
degit@2.8.4:
resolution: {integrity: sha512-vqYuzmSA5I50J882jd+AbAhQtgK6bdKUJIex1JNfEUPENCgYsxugzKVZlFyMwV4i06MmnV47/Iqi5Io86zf3Ng==}
engines: {node: '>=8.0.0'}
hasBin: true
dot-case@2.1.1:
resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==}
ejs@3.1.10:
resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
engines: {node: '>=0.10.0'}
hasBin: true
enquirer@2.4.1:
resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
engines: {node: '>=8.6'}
esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
hasBin: true
execa@5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'}
filelist@1.0.4:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
front-matter@4.0.2:
resolution: {integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==}
fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'}
get-stream@6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
header-case@1.0.1:
resolution: {integrity: sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==}
human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
hygen@6.2.11:
resolution: {integrity: sha512-t6/zLI2XozP5gvV74nnl8LZSbwpVNFUkUs/O9DwuOdiiBbws5k4AQNVwKZ9FGzcKjdJ5EBBYkVzlcUHkLyY0FQ==}
hasBin: true
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore-walk@4.0.1:
resolution: {integrity: sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw==}
engines: {node: '>=10'}
inflection@1.13.4:
resolution: {integrity: sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==}
engines: {'0': node >= 0.4.0}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
is-lower-case@1.1.3:
resolution: {integrity: sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==}
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
is-upper-case@1.1.2:
resolution: {integrity: sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jake@10.9.2:
resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
engines: {node: '>=10'}
hasBin: true
js-yaml@3.14.1:
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
hasBin: true
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
log-symbols@4.1.0:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
lower-case-first@1.0.2:
resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==}
lower-case@1.1.4:
resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==}
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
no-case@2.3.2:
resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==}
npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
onetime@5.1.2:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
ora@5.4.1:
resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
engines: {node: '>=10'}
param-case@2.1.1:
resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
pascal-case@2.0.1:
resolution: {integrity: sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==}
path-case@2.1.1:
resolution: {integrity: sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
restore-cursor@3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
sentence-case@2.1.1:
resolution: {integrity: sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
snake-case@2.1.0:
resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==}
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-final-newline@2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'}
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
swap-case@1.1.2:
resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==}
title-case@2.1.1:
resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==}
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
upper-case-first@1.1.2:
resolution: {integrity: sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==}
upper-case@1.1.3:
resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
snapshots:
'@types/node@17.0.45': {}
ansi-colors@4.1.3: {}
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
argparse@1.0.10:
dependencies:
sprintf-js: 1.0.3
async@3.2.5: {}
balanced-match@1.0.2: {}
base64-js@1.5.1: {}
bl@4.1.0:
dependencies:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
buffer@5.7.1:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
camel-case@3.0.0:
dependencies:
no-case: 2.3.2
upper-case: 1.1.3
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
change-case@3.1.0:
dependencies:
camel-case: 3.0.0
constant-case: 2.0.0
dot-case: 2.1.1
header-case: 1.0.1
is-lower-case: 1.1.3
is-upper-case: 1.1.2
lower-case: 1.1.4
lower-case-first: 1.0.2
no-case: 2.3.2
param-case: 2.1.1
pascal-case: 2.0.1
path-case: 2.1.1
sentence-case: 2.1.1
snake-case: 2.1.0
swap-case: 1.1.2
title-case: 2.1.1
upper-case: 1.1.3
upper-case-first: 1.1.2
cli-cursor@3.1.0:
dependencies:
restore-cursor: 3.1.0
cli-spinners@2.9.2: {}
clone@1.0.4: {}
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
concat-map@0.0.1: {}
constant-case@2.0.0:
dependencies:
snake-case: 2.1.0
upper-case: 1.1.3
cross-spawn@7.0.3:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
debug@4.3.6:
dependencies:
ms: 2.1.2
defaults@1.0.4:
dependencies:
clone: 1.0.4
degit@2.8.4: {}
dot-case@2.1.1:
dependencies:
no-case: 2.3.2
ejs@3.1.10:
dependencies:
jake: 10.9.2
enquirer@2.4.1:
dependencies:
ansi-colors: 4.1.3
strip-ansi: 6.0.1
esprima@4.0.1: {}
execa@5.1.1:
dependencies:
cross-spawn: 7.0.3
get-stream: 6.0.1
human-signals: 2.1.0
is-stream: 2.0.1
merge-stream: 2.0.0
npm-run-path: 4.0.1
onetime: 5.1.2
signal-exit: 3.0.7
strip-final-newline: 2.0.0
filelist@1.0.4:
dependencies:
minimatch: 5.1.6
front-matter@4.0.2:
dependencies:
js-yaml: 3.14.1
fs-extra@10.1.0:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.1
get-stream@6.0.1: {}
graceful-fs@4.2.11: {}
has-flag@4.0.0: {}
header-case@1.0.1:
dependencies:
no-case: 2.3.2
upper-case: 1.1.3
human-signals@2.1.0: {}
hygen@6.2.11:
dependencies:
'@types/node': 17.0.45
chalk: 4.1.2
change-case: 3.1.0
debug: 4.3.6
degit: 2.8.4
ejs: 3.1.10
enquirer: 2.4.1
execa: 5.1.1
front-matter: 4.0.2
fs-extra: 10.1.0
ignore-walk: 4.0.1
inflection: 1.13.4
ora: 5.4.1
yargs-parser: 21.1.1
transitivePeerDependencies:
- supports-color
ieee754@1.2.1: {}
ignore-walk@4.0.1:
dependencies:
minimatch: 3.1.2
inflection@1.13.4: {}
inherits@2.0.4: {}
is-interactive@1.0.0: {}
is-lower-case@1.1.3:
dependencies:
lower-case: 1.1.4
is-stream@2.0.1: {}
is-unicode-supported@0.1.0: {}
is-upper-case@1.1.2:
dependencies:
upper-case: 1.1.3
isexe@2.0.0: {}
jake@10.9.2:
dependencies:
async: 3.2.5
chalk: 4.1.2
filelist: 1.0.4
minimatch: 3.1.2
js-yaml@3.14.1:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
jsonfile@6.1.0:
dependencies:
universalify: 2.0.1
optionalDependencies:
graceful-fs: 4.2.11
log-symbols@4.1.0:
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
lower-case-first@1.0.2:
dependencies:
lower-case: 1.1.4
lower-case@1.1.4: {}
merge-stream@2.0.0: {}
mimic-fn@2.1.0: {}
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
minimatch@5.1.6:
dependencies:
brace-expansion: 2.0.1
ms@2.1.2: {}
no-case@2.3.2:
dependencies:
lower-case: 1.1.4
npm-run-path@4.0.1:
dependencies:
path-key: 3.1.1
onetime@5.1.2:
dependencies:
mimic-fn: 2.1.0
ora@5.4.1:
dependencies:
bl: 4.1.0
chalk: 4.1.2
cli-cursor: 3.1.0
cli-spinners: 2.9.2
is-interactive: 1.0.0
is-unicode-supported: 0.1.0
log-symbols: 4.1.0
strip-ansi: 6.0.1
wcwidth: 1.0.1
param-case@2.1.1:
dependencies:
no-case: 2.3.2
pascal-case@2.0.1:
dependencies:
camel-case: 3.0.0
upper-case-first: 1.1.2
path-case@2.1.1:
dependencies:
no-case: 2.3.2
path-key@3.1.1: {}
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
restore-cursor@3.1.0:
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
safe-buffer@5.2.1: {}
sentence-case@2.1.1:
dependencies:
no-case: 2.3.2
upper-case-first: 1.1.2
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
signal-exit@3.0.7: {}
snake-case@2.1.0:
dependencies:
no-case: 2.3.2
sprintf-js@1.0.3: {}
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-final-newline@2.0.0: {}
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
swap-case@1.1.2:
dependencies:
lower-case: 1.1.4
upper-case: 1.1.3
title-case@2.1.1:
dependencies:
no-case: 2.3.2
upper-case: 1.1.3
universalify@2.0.1: {}
upper-case-first@1.1.2:
dependencies:
upper-case: 1.1.3
upper-case@1.1.3: {}
util-deprecate@1.0.2: {}
wcwidth@1.0.1:
dependencies:
defaults: 1.0.4
which@2.0.2:
dependencies:
isexe: 2.0.0
yargs-parser@21.1.1: {}

View File

@@ -1,49 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module RuboCop
module Cop
module Zammad
module CanCheck51PlusMigrations
VERSION_THRESHOLD = 5.1
# Apply to 7.0 migrations and newer
MIGRATION_THRESHOLD = 20250519095403 # rubocop:disable Style/NumericLiterals
def migration_51_or_newer?(node)
return false if old_migration?
migration?(node) && rails_51_or_newer?(node)
end
private
def migration?(node)
superclass(node)&.children&.at(0)&.const_name == 'ActiveRecord::Migration'
end
def old_migration?
file_timestamp = File.basename(processed_source.file_path)[%r{\A\d+}]
return false if !file_timestamp
file_timestamp.to_i < MIGRATION_THRESHOLD
end
def rails_51_or_newer?(node)
klass = superclass(node)
return false if !klass.send_type?
klass.arguments.first.value.to_f >= VERSION_THRESHOLD
end
def superclass(node)
node
.each_ancestor(:class)
.first
&.parent_class
end
end
end
end
end

View File

@@ -1,19 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
require_relative 'can_check_namespace'
module RuboCop
module Cop
module Zammad
module CanCheckGqlOperation
include CanCheckNamespace
DISALLOW_IN = %w[Gql::Mutations Gql::Queries Gql::Subscriptions].freeze
def inside_gql_operation?(node)
inside_namespace?(node, *DISALLOW_IN)
end
end
end
end
end

View File

@@ -1,21 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module RuboCop
module Cop
module Zammad
module CanCheckNamespace
def namespace_path(node)
node.each_ancestor(:class, :module).filter_map do |ancestor|
ancestor.identifier&.const_name
end
end
def inside_namespace?(node, *namespaces)
path = namespace_path(node).reverse.join('::')
namespaces.any? { |ns| path.start_with?(ns) }
end
end
end
end
end

View File

@@ -17,10 +17,10 @@ module RuboCop
PATTERN
def_node_matcher :has_reset?, <<-PATTERN
$(send _ {:describe :context :it :shared_examples} (_ ...) (hash <(pair (sym :db_strategy) (sym {:reset})) ...> ))
$(send _ {:describe :context :it :shared_examples} (_ ...) (hash <(pair (sym :db_strategy) (sym {:reset :reset_all})) ...> ))
PATTERN
MSG = 'Add a `db_strategy: :reset` to your context/describe when you are creating object manager attributes!'.freeze
MSG = 'Add a `db_strategy: :reset` to your context/decribe when you are creating object manager attributes!'.freeze
def on_send(node)
return if !migration_execute?(node) && !create_attribute?(node)

View File

@@ -1,28 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module RuboCop
module Cop
module Zammad
class ForbidCallingServiceDirectly < RuboCop::Cop::Base
MSG = 'Service classes can only be called with `execute` class method or by chaining `Service.with_current_user(user).execute`. Calling `new` is forbidden.'.freeze
def on_send(node)
receiver = node.receiver
return if !receiver
return if !receiver.source.start_with?('Service::')
case node.method_name
when :new
add_offense(node, message: MSG)
when :with_current_user
if node.parent&.send_type? && node.parent.method_name == :execute
return
end
add_offense(node, message: MSG)
end
end
end
end
end
end

View File

@@ -1,19 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module RuboCop
module Cop
module Zammad
class ForbidLoofahDocument < Base
MSG = 'Do not use Loofah.document. Use explicit parser version'.freeze
def_node_matcher :loofah_document?, <<~PATTERN
(send (const nil? :Loofah) :document)
PATTERN
def on_send(node)
add_offense(node, message: MSG) if loofah_document?(node)
end
end
end
end
end

View File

@@ -1,19 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module RuboCop
module Cop
module Zammad
class ForbidLoofahFragment < Base
MSG = 'Do not use Loofah.fragment. Use explicit parser version'.freeze
def_node_matcher :loofah_fragment?, <<~PATTERN
(send (const nil? :Loofah) :fragment)
PATTERN
def on_send(node)
add_offense(node, message: MSG) if loofah_fragment?(node)
end
end
end
end
end

View File

@@ -1,36 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module RuboCop
module Cop
module Zammad
# `Exceptions::UnprocessableEntity` is deprecated and will be removed in Zammad 8.0.
# Use `Exceptions::UnprocessableContent` instead.
#
# @example
# # bad
# raise Exceptions::UnprocessableEntity, 'message'
# rescue Exceptions::UnprocessableEntity => e
#
# # good
# raise Exceptions::UnprocessableContent, 'message'
# rescue Exceptions::UnprocessableContent => e
class ForbidUnprocessableEntity < Base
extend AutoCorrector
MSG = 'Use `Exceptions::UnprocessableContent` instead of the deprecated `Exceptions::UnprocessableEntity`.'.freeze
def_node_matcher :unprocessable_entity?, <<~PATTERN
(const (const {nil? cbase} :Exceptions) :UnprocessableEntity)
PATTERN
def on_const(node)
return if !unprocessable_entity?(node)
add_offense(node, message: MSG) do |corrector|
corrector.replace(node.loc.name, 'UnprocessableContent')
end
end
end
end
end
end

View File

@@ -1,55 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
require_relative 'concerns/can_check_gql_operation'
module RuboCop
module Cop
module Zammad
class GraphqlAuthorizeAllowPublicAccess < Base
include CanCheckGqlOperation
extend AutoCorrector
MSG = 'Replace `authorize` method that returns `true` with `allow_public_access!`.'.freeze
def on_defs(node)
return if !inside_gql_operation?(node)
# Check for: def self.authorize(...) true end
return if node.method_name != :authorize
return if !returns_true?(node.body)
add_offense(node) do |corrector|
corrector.replace(node, 'allow_public_access!')
end
end
def on_def(node)
return if !inside_gql_operation?(node)
# Check for: def authorize(...) true end
return if node.method_name != :authorize
return if !returns_true?(node.body)
add_offense(node) do |corrector|
corrector.replace(node, 'allow_public_access!')
end
end
private
def returns_true?(body)
return false if body.nil?
# Handle simple case: def authorize(...); true; end
return true if body.type == :true # rubocop:disable Lint/BooleanSymbol
# Handle multi-statement case: def authorize(...); puts "foo"; true; end
if body.type == :begin
last_statement = body.children.last
return last_statement&.type == :true # rubocop:disable Lint/BooleanSymbol
end
false
end
end
end
end
end

View File

@@ -1,22 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
require_relative 'concerns/can_check_gql_operation'
module RuboCop
module Cop
module Zammad
class GraphqlAuthorizeDisallowed < Base
include CanCheckGqlOperation
MSG = 'GraphQL operations must not define an `self.authorize` method. Use helpers or authorized?'.freeze
def on_defs(node)
return if !inside_gql_operation?(node)
return if node.method_name != :authorize
add_offense(node.loc.name)
end
end
end
end
end

View File

@@ -1,73 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
require_relative 'concerns/can_check_gql_operation'
module RuboCop
module Cop
module Zammad
class GraphqlAuthorizeRequiresPermission < Base
include CanCheckGqlOperation
extend AutoCorrector
MSG = 'Replace `authorize` method with `requires_permission` when checking user permission.'.freeze
def on_def(node)
return if !inside_gql_operation?(node)
return if node.method_name != :authorize
handle_authorize_method(node)
end
def on_defs(node)
return if !inside_gql_operation?(node)
return if node.method_name != :authorize
handle_authorize_method(node)
end
private
def handle_authorize_method(node)
permission_name = extract_permission(node)
return if permission_name.nil?
add_offense(node) do |corrector|
corrector.replace(node, "requires_permission #{permission_name.source}")
end
end
def extract_permission(method_node)
body = method_node.body
return nil if body.nil?
return nil if !valid_permissions_call?(body)
return nil if !valid_receiver_chain?(body)
return nil if !valid_method_parameter?(body, method_node)
arg = body.first_argument
arg if arg&.str_type?
end
def valid_permissions_call?(body)
body.send_type? && body.method_name == :permissions?
end
def valid_receiver_chain?(body)
receiver = body.receiver
return false if !receiver&.send_type?
return false if receiver.method_name != :current_user
param_receiver = receiver.receiver
param_receiver&.send_type? == false && %i[lvar identifier].include?(param_receiver.type)
end
def valid_method_parameter?(body, method_node)
param_receiver = body.receiver.receiver
param_name = param_receiver.children.last
method_params = method_node.arguments.map(&:name)
method_params.include?(param_name)
end
end
end
end
end

View File

@@ -1,100 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
require_relative 'concerns/can_check_gql_operation'
module RuboCop
module Cop
module Zammad
class GraphqlAuthorizedRequiresEnabledSetting < Base
include CanCheckGqlOperation
extend AutoCorrector
MSG = 'Replace `authorized?` method with `requires_enabled_setting` when checking Setting.get.'.freeze
def on_def(node)
return if !inside_gql_operation?(node)
return if node.method_name != :authorized?
handle_authorized_method(node)
end
def on_defs(node)
return if !inside_gql_operation?(node)
return if node.method_name != :authorized?
handle_authorized_method(node)
end
private
def handle_authorized_method(node)
setting_names = collect_setting_names(node.body)
return if setting_names.empty?
return if other_calls?(node.body)
add_offense(node) do |corrector|
replacement = setting_names
.map { |name| "requires_enabled_setting #{name.source}" }
.join("\n#{' ' * node.loc.column}")
corrector.replace(node, replacement)
end
end
def collect_setting_names(body)
return [] if body.nil?
setting_names = []
traverse_nodes(body) do |node|
next if !node.send_type?
next if node.method_name != :get
next if !node.receiver&.const_type?
# Get the constant name - for `Setting`, it's the last child
const_name = node.receiver.children.last
next if const_name != :Setting
arg = node.first_argument
setting_names << arg if arg&.str_type?
end
setting_names
end
def other_calls?(body)
return false if body.nil?
has_other = false
traverse_nodes(body) do |node|
# Allow Setting.get calls
if node.send_type? && node.method_name == :get &&
node.receiver&.const_type? && node.receiver.children.last == :Setting
next
end
# Allow super
next if node.zsuper_type?
# If we see any other send node, it's an additional method call
if node.send_type?
has_other = true
break
end
end
has_other
end
def traverse_nodes(node, &)
yield node if node.is_a?(RuboCop::AST::Node)
node.children.each do |child|
traverse_nodes(child, &) if child.is_a?(RuboCop::AST::Node)
end
end
end
end
end
end

View File

@@ -1,55 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
require_relative 'concerns/can_check_gql_operation'
module RuboCop
module Cop
module Zammad
class GraphqlCsrfSkipVerification < Base
include CanCheckGqlOperation
extend AutoCorrector
MSG = 'Replace `requires_csrf_verification?` method that returns `false` with `skip_csrf_verification!`.'.freeze
def on_defs(node)
return if !inside_gql_operation?(node)
# Check for: def self.requires_csrf_verification?(...) false end
return if node.method_name != :requires_csrf_verification?
return if !returns_false?(node.body)
add_offense(node) do |corrector|
corrector.replace(node, 'skip_csrf_verification!')
end
end
def on_def(node)
return if !inside_gql_operation?(node)
# Check for: def requires_csrf_verification?(...) false end
return if node.method_name != :requires_csrf_verification?
return if !returns_false?(node.body)
add_offense(node) do |corrector|
corrector.replace(node, 'skip_csrf_verification!')
end
end
private
def returns_false?(body)
return false if body.nil?
# Handle simple case: def requires_csrf_verification?; false; end
return true if body.type == :false # rubocop:disable Lint/BooleanSymbol
# Handle multi-statement case: def requires_csrf_verification?; puts "foo"; false; end
if body.type == :begin
last_statement = body.children.last
return last_statement&.type == :false # rubocop:disable Lint/BooleanSymbol
end
false
end
end
end
end
end

View File

@@ -1,55 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module RuboCop
module Cop
module Zammad
class GraphqlForbidSensitiveFields < Base
MSG = 'Avoid declaring sensitive field `:%{name}` in a GraphQL return type — it may leak secrets to the API consumer. Use `# rubocop:disable Zammad/GraphqlForbidSensitiveFields` with a justification if intentional.'.freeze
# Inspired by config/initializers/filter_parameter_logging.rb.
# Matched as substrings against the (snake_case) field name —
# `passw` therefore also catches `password`/`passwd`, and `cert`
# also catches `certificate`. The `_key` substring from the Rails
# filter list is too broad for a static check (e.g. `cache_key`,
# `foreign_key`), so concrete sensitive `_key` names are listed.
SENSITIVE_SUBSTRINGS = %w[
passw
passphrase
secret
token
api_key
private_key
auth_key
access_key
master_key
signing_key
crypt
salt
cert
otp
ssn
cvv
cvc
credential
bind_pw
].freeze
# Match `field` and the relation helpers in HasModelRelations
# (belongs_to/has_one/lookup_field), which all forward to `field`
# under the hood and therefore also expose a return-type field.
def_node_matcher :graphql_field?, <<~PATTERN
(send nil? {:field :belongs_to :has_one :lookup_field} (sym $_) ...)
PATTERN
def on_send(node)
graphql_field?(node) do |field_name|
name_string = field_name.to_s
next if SENSITIVE_SUBSTRINGS.none? { |needle| name_string.include?(needle) }
add_offense(node.first_argument, message: format(MSG, name: name_string))
end
end
end
end
end
end

View File

@@ -1,53 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
# frozen_string_literal: true
require_relative 'concerns/can_check_51_plus_migrations'
module RuboCop
module Cop
module Zammad
class MigrationEnsureIntegerForeignKey < Base
extend AutoCorrector
include CanCheck51PlusMigrations
MSG = 'Rails 5.1+ migrations must add type: :integer to references setup.'
RESTRICT_ON_SEND = %i[
references
belongs_to
add_reference
add_references
].freeze
def on_send(node)
return if !migration_51_or_newer?(node)
options = node.arguments.last
pair_with_type = nil
if options&.hash_type?
options.pairs.each do |pair|
next if !(pair.key.sym_type? && pair.key.value == :type)
pair_with_type = pair
next if pair.value.source == ':integer'
next if pair.value.source == ':bigint' # allow explicit bigint foreign keys
add_offense(pair.value) do |corrector|
corrector.replace(pair.value, ':integer')
end
end
end
return if pair_with_type
add_offense(node) do |corrector|
corrector.insert_after(node.arguments.last.source_range, ', type: :integer')
end
end
end
end
end
end

View File

@@ -1,49 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
# frozen_string_literal: true
require_relative 'concerns/can_check_51_plus_migrations'
module RuboCop
module Cop
module Zammad
class MigrationEnsureIntegerPrimaryKey < Base
extend AutoCorrector
include CanCheck51PlusMigrations
MSG = 'Rails 5.1+ migrations must add id: :integer to ensure primary key is integer, not bigint.'
RESTRICT_ON_SEND = %i[create_table].freeze
def on_send(node)
return if !migration_51_or_newer?(node)
options = node.arguments.last
pair_with_id = nil
if options&.hash_type?
options.pairs.each do |pair|
next if !(pair.key.sym_type? && pair.key.value == :id)
pair_with_id = pair
next if pair.value.source == ':integer'
next if pair.value.source == 'false' # allow id: false for ID-less tables
next if pair.value.source == ':bigserial' # allow id: :bigserial for explicit bigint primary keys
add_offense(pair.value) do |corrector|
corrector.replace(pair.value, ':integer')
end
end
end
return if pair_with_id
add_offense(node) do |corrector|
corrector.insert_after(node.arguments.first.source_range, ', id: :integer')
end
end
end
end
end
end

View File

@@ -1,36 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module RuboCop
module Cop
module Zammad
#
#
# @example
# # bad
# ActiveRecord::Base.descendants
# ActiveRecord::Base
# .descendants
#
# # good
# Models.all.keys
class PreferModelsAll < Base
extend AutoCorrector
def_node_matcher :active_record_descendants?, <<-PATTERN
$(send (const (const {nil? cbase} :ActiveRecord) :Base) :descendants)
PATTERN
MSG = 'Prefer `Models.all.keys` over `ActiveRecord::Base.descendants` to avoid issues with eager loading.'.freeze
def on_send(node)
return if active_record_descendants?(node).nil?
add_offense(node) do |corrector|
corrector.replace(node, 'Models.all.keys')
end
end
end
end
end
end

View File

@@ -2,20 +2,19 @@
# https://github.com/rubocop-hq/rubocop/blob/master/config/default.yml
require:
- rubocop-capybara
- rubocop-factory_bot
- rubocop-graphql
- rubocop-inflector
- rubocop-rspec_rails
- ../../config/initializers/inflections.rb
- ./rubocop_zammad.rb
plugins:
- rubocop-capybara
- rubocop-factory_bot
- rubocop-faker
- rubocop-graphql
- rubocop-performance
- rubocop-rails
- rubocop-rake
- rubocop-rspec
- rubocop-rspec_rails
inherit_from:
- todo.yml
@@ -29,16 +28,16 @@ AllCops:
NewCops: enable
DisplayCopNames: true
Include:
- ".gitlab/**/*.rb"
- ".github/**/*.rb"
- ".dev/rubocop/**/*.rb"
- '.gitlab/**/*.rb'
- '.github/**/*.rb'
- '.dev/rubocop/**/*.rb'
Exclude:
- "bin/*"
- "db/schema.rb"
- "vendor/**/*"
- "node_modules/**/*"
- "public/assets/**/*"
- "public/packs/**/*"
- 'bin/*'
- 'db/schema.rb'
- 'vendor/**/*'
- 'node_modules/**/*'
- 'public/assets/**/*'
- 'public/packs/**/*'
RSpec:
Include:
@@ -46,11 +45,11 @@ RSpec:
- "**/*_examples.rb"
Language:
Expectations:
- expect_no_offenses
- expect_offense
- adds_foreign_key
- sent
- not_sent
- expect_no_offenses
- expect_offense
- adds_foreign_key
- sent
- not_sent
Rails:
Enabled: true
@@ -61,40 +60,40 @@ Style/FrozenStringLiteralComment:
Enabled: false
Layout/LineLength:
Description: "Limit lines to 80 characters."
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#80-character-limits"
Description: 'Limit lines to 80 characters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
Enabled: false
Style/NegatedIf:
Description: >-
Favor unless over if for negative conditions
(or control flow or).
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#unless-for-negatives"
Favor unless over if for negative conditions
(or control flow or).
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives'
Enabled: false
Style/IfUnlessModifier:
Description: >-
Favor modifier if/unless usage when you have a
single-line body.
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier"
Favor modifier if/unless usage when you have a
single-line body.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier'
Enabled: false
Style/TrailingCommaInArrayLiteral:
Description: "Checks for trailing comma in array literals."
StyleGuide: "#no-trailing-array-commas"
Description: 'Checks for trailing comma in array literals.'
StyleGuide: '#no-trailing-array-commas'
Enabled: false
Style/TrailingCommaInHashLiteral:
Description: "Checks for trailing comma in hash literals."
Description: 'Checks for trailing comma in hash literals.'
Enabled: false
Style/TrailingCommaInArguments:
Description: "Checks for trailing comma in argument lists."
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas"
Description: 'Checks for trailing comma in argument lists.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
Enabled: false
Style/RescueStandardError:
Description: "Avoid rescuing without specifying an error class."
Description: 'Avoid rescuing without specifying an error class.'
Enabled: true
EnforcedStyle: implicit
@@ -104,16 +103,16 @@ Style/FormatStringToken:
# Prefer %{...} over %<...>s to make it easier for translators.
Layout/SpaceInsideReferenceBrackets:
Description: "Checks the spacing inside referential brackets."
Description: 'Checks the spacing inside referential brackets.'
Enabled: false
Layout/SpaceInsideArrayLiteralBrackets:
Description: "Checks the spacing inside array literal brackets."
Description: 'Checks the spacing inside array literal brackets.'
Enabled: false
Style/DefWithParentheses:
Description: "Use def with parentheses when there are arguments."
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#method-parens"
Description: 'Use def with parentheses when there are arguments.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
Enabled: false
Style/HashSyntax:
@@ -121,8 +120,8 @@ Style/HashSyntax:
Layout/HashAlignment:
Description: >-
Align the elements of a hash literal if they span more than
one line.
Align the elements of a hash literal if they span more than
one line.
Enabled: true
EnforcedHashRocketStyle: table
EnforcedColonStyle: table
@@ -145,75 +144,75 @@ Layout/EmptyLinesAroundModuleBody:
Enabled: false
Layout/ExtraSpacing:
Description: "Do not use unnecessary spacing."
Description: 'Do not use unnecessary spacing.'
Enabled: true
Exclude:
- "config/routes/**/*"
- "db/migrate/20120101000001_create_base.rb"
- "db/migrate/20120101000010_create_ticket.rb"
- 'config/routes/**/*'
- 'db/migrate/20120101000001_create_base.rb'
- 'db/migrate/20120101000010_create_ticket.rb'
Style/MultilineBlockChain:
Description: "Avoid multi-line chains of blocks."
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#single-line-blocks"
Description: 'Avoid multi-line chains of blocks.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
Enabled: false
Metrics/ClassLength:
Description: "Avoid classes longer than 100 lines of code."
Description: 'Avoid classes longer than 100 lines of code.'
Enabled: false
Metrics/MethodLength:
Description: "Avoid methods longer than 10 lines of code."
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#short-methods"
Description: 'Avoid methods longer than 10 lines of code.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
Enabled: false
Metrics/ParameterLists:
Description: "Checks for methods with too many parameters."
Description: 'Checks for methods with too many parameters.'
CountKeywordArgs: false
Style/BlockComments:
Description: "Do not use block comments."
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-block-comments"
Description: 'Do not use block comments.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments'
Enabled: false
Style/PerlBackrefs:
Description: "Avoid Perl-style regex back references."
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers"
Description: 'Avoid Perl-style regex back references.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers'
Enabled: false
Rails/ApplicationRecord:
Exclude:
- "app/models/application_model.rb"
- "app/models/active_job_lock.rb"
- 'app/models/application_model.rb'
- 'app/models/active_job_lock.rb'
Rails/BulkChangeTable:
Description: "Check whether alter queries are combinable."
Description: 'Check whether alter queries are combinable.'
Enabled: false
Rails/FindEach:
Description: "Prefer all.find_each over all.find."
Description: 'Prefer all.find_each over all.find.'
Enabled: false
Rails/HasAndBelongsToMany:
Description: "Prefer has_many :through to has_and_belongs_to_many."
# StyleGuide: 'https://github.com/bbatsov/rails-style-guide#has-many-through'
Description: 'Prefer has_many :through to has_and_belongs_to_many.'
# StyleGuide: 'https://github.com/bbatsov/rails-style-guide#has-many-through'
Enabled: false
Rails/I18nLocaleTexts:
Description: "Enforces use of I18n and locale files instead of locale specific strings."
Description: 'Enforces use of I18n and locale files instead of locale specific strings.'
Enabled: false
Rails/MatchRoute:
Description: >-
Don't use `match` to define any routes unless there is a need to map multiple request types
among [:get, :post, :patch, :put, :delete] to a single action using the `:via` option.
StyleGuide: "https://rails.rubystyle.guide/#no-match-routes"
Don't use `match` to define any routes unless there is a need to map multiple request types
among [:get, :post, :patch, :put, :delete] to a single action using the `:via` option.
StyleGuide: 'https://rails.rubystyle.guide/#no-match-routes'
Enabled: false
Rails/SkipsModelValidations:
Description: >-
Use methods that skips model validations with caution.
See reference for more information.
Reference: "http://guides.rubyonrails.org/active_record_validations.html#skipping-validations"
Use methods that skips model validations with caution.
See reference for more information.
Reference: 'http://guides.rubyonrails.org/active_record_validations.html#skipping-validations'
Enabled: true
Exclude:
- test/**/*
@@ -225,16 +224,13 @@ Rails/ReversibleMigration:
Enabled: false
Rails/EnvironmentVariableAccess:
Description: "This cop looks for direct access to environment variables through the ENV variable within the application code."
Description: 'This cop looks for direct access to environment variables through the ENV variable within the application code.'
Enabled: true
AllowReads: true
Exclude:
- "spec/lib/**/*"
Rails/Output:
Exclude:
- "lib/tasks/**/*"
- "spec/**/*"
Include:
- "**/*_spec.rb"
@@ -243,7 +239,7 @@ Rails/Delegate:
Enabled: false
Style/ClassAndModuleChildren:
Description: "Checks style of children classes and modules."
Description: 'Checks style of children classes and modules.'
Enabled: true
EnforcedStyle: compact
# Avoids nesting of classes and modules in sequencer code and improves readability by decreasing whitespace.
@@ -252,64 +248,70 @@ Style/ClassAndModuleChildren:
# - changes to the lexical scope which require reference to the complete namespace of external classes
# - difficult backporting due to extensive whitespace changes on all lines
Include:
- "lib/sequencer/**/*.rb"
- 'lib/sequencer/**/*.rb'
Naming/FileName:
Description: "Use snake_case for source file names."
StyleGuide: "https://github.com/bbatsov/ruby-style-guide#snake-case-files"
Description: 'Use snake_case for source file names.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
Enabled: true
Exclude:
- "script/websocket-server.rb"
- 'script/websocket-server.rb'
Naming/VariableNumber:
Description: "Use the configured style when numbering variables."
Description: 'Use the configured style when numbering variables.'
Enabled: false
Naming/MethodParameterName:
Description: >-
Checks for method parameter names that contain capital letters,
end in numbers, or do not meet a minimal length.
Checks for method parameter names that contain capital letters,
end in numbers, or do not meet a minimal length.
Enabled: true
AllowedNames: e, id, _, ip, to
Lint/BooleanSymbol:
Description: "Check for `:true` and `:false` symbols."
Description: 'Check for `:true` and `:false` symbols.'
Enabled: true
Exclude:
- "db/seeds/object_manager_attributes.rb"
- "spec/requests/integration/object_manager_attributes_spec.rb"
Lint/InterpolationCheck:
Description: "Raise warning for interpolation in single q strs"
Description: 'Raise warning for interpolation in single q strs'
Enabled: true
Exclude:
- "spec/models/trigger/base_spec.rb"
- "test/unit/email_process_auto_response_test.rb"
- "test/unit/email_process_bounce_delivery_permanent_failed_test.rb"
- "test/unit/email_process_bounce_follow_test.rb"
- "test/unit/notification_factory_renderer_test.rb"
- "test/unit/notification_factory_template_test.rb"
- "test/unit/ticket_trigger_test.rb"
- "test/unit/ticket_trigger_recursive_disabled_test.rb"
Lint/UselessConstantScoping:
Description: Checks for useless constant scoping.
Enabled: false
Style/StringConcatenation:
Description: "Checks for places where string concatenation can be replaced with string interpolation."
StyleGuide: "#string-interpolation"
Description: 'Checks for places where string concatenation can be replaced with string interpolation.'
StyleGuide: '#string-interpolation'
Enabled: true
Exclude:
- "config/routes/**/*"
Style/RegexpLiteral:
Description: "This cop enforces using // or %r around regular expressions."
Description: 'This cop enforces using // or %r around regular expressions.'
EnforcedStyle: percent_r
Style/RedundantBegin:
Description: "This cop checks for redundant `begin` blocks."
Description: 'This cop checks for redundant `begin` blocks.'
Enabled: false
Style/FetchEnvVar:
Description: "This cop suggests `ENV.fetch` for the replacement of `ENV[]`."
Description: 'This cop suggests `ENV.fetch` for the replacement of `ENV[]`.'
Enabled: false
Style/SafeNavigationChainLength:
Description: "This cops suggests to not use&.optional&.chaining&.too&.much"
Description: 'This cops suggests to not use&.optional&.chaining&.too&.much'
Enabled: false
GraphQL/OrderedArguments:
@@ -326,7 +328,7 @@ GraphQL/ExtractInputType:
MaxArguments: 3
# Run cop only for mutations: https://github.com/DmitryTsepelev/rubocop-graphql/issues/47
Include:
- "app/graphql/gql/mutations/**/*.rb"
- 'app/graphql/gql/mutations/**/*.rb'
# Enforcing field descriptions for all model fields will be too verbose/redundant.
GraphQL/FieldDescription:
@@ -337,14 +339,13 @@ GraphQL/ObjectDescription:
Exclude:
- "app/graphql/gql/fields/**/*" # false positive?
- "app/graphql/gql/types/**/base_*"
- "app/graphql/gql/types/**/extensions/**/*" # union extension classes are no GraphQL objects
# RSpec tests
Style/NumericPredicate:
Description: >-
Checks for the use of predicate- or comparison methods for
numeric comparisons.
StyleGuide: "#predicate-methods"
Checks for the use of predicate- or comparison methods for
numeric comparisons.
StyleGuide: '#predicate-methods'
# This will change to a new method call which isn't guaranteed to be on the
# object. Switching these methods has to be done with knowledge of the types
# of the variables which rubocop doesn't have.
@@ -355,9 +356,9 @@ Style/NumericPredicate:
Lint/AmbiguousBlockAssociation:
Description: >-
Checks for ambiguous block association with method when param
passed without parentheses.
StyleGuide: "#syntax"
Checks for ambiguous block association with method when param
passed without parentheses.
StyleGuide: '#syntax'
Enabled: true
Exclude:
- "spec/support/*.rb"
@@ -366,8 +367,8 @@ Lint/AmbiguousBlockAssociation:
Layout/MultilineMethodCallIndentation:
Description: >-
Checks the indentation of the method name part in method calls
that span more than one line.
Checks the indentation of the method name part in method calls
that span more than one line.
EnforcedStyle: indented
Include:
- "**/*_spec.rb"
@@ -375,17 +376,13 @@ Layout/MultilineMethodCallIndentation:
Lint/UnusedMethodArgument:
AllowUnusedKeywordArguments: true
Naming/PredicatePrefix:
Naming/PredicateName:
Exclude:
- "app/graphql/gql/types/**/*"
Naming/PredicateMethod:
Description: "Checks for methods returning true/false that do not end with a questionmark."
Enabled: false
RSpec/DescribeMethod:
# We want to be able to split tests into subfolders, where several cases are grouped together.
Description: "Checks that the second argument to the top level describe is the tested method name."
Description: 'Checks that the second argument to the top level describe is the tested method name.'
Enabled: false
RSpec/NestedGroups:
@@ -396,45 +393,48 @@ RSpecRails/AvoidSetupHook:
- "test/**/*"
RSpec/AlignLeftLetBrace:
Description: "Checks that left braces for adjacent single line lets are aligned."
Description: 'Checks that left braces for adjacent single line lets are aligned.'
Enabled: true
RSpec/AnyInstance:
Description: "Prefer instance doubles over stubbing any instance of a class."
Description: 'Prefer instance doubles over stubbing any instance of a class.'
Enabled: false
RSpec/SubjectStub:
Enabled: false
RSpecRails/InferredSpecType:
Description: "Identifies redundant spec type."
Enabled: false # We use types to add DSL to rspec.
Description: 'Identifies redundant spec type.'
Enabled: false # We use types to add DSL to rspec.
RSpec/IndexedLet:
Description: "Do not set up test data using indexes (e.g., `item_1`, `item_2`)."
Description: 'Do not set up test data using indexes (e.g., `item_1`, `item_2`).'
Enabled: false
Capybara/SpecificFinders:
Description: "Checks if there is a more specific finder offered by Capybara."
Description: 'Checks if there is a more specific finder offered by Capybara.'
Enabled: false
# False positives, autocorrection makes weird suggestions.
Capybara/SpecificActions:
Description: "Checks for there is a more specific actions offered by Capybara."
Description: 'Checks for there is a more specific actions offered by Capybara.'
Enabled: false
Capybara/RSpec/NegationMatcher:
Capybara/NegationMatcher:
Description: Enforces use of have_no_* or not_to for negated expectations.
EnforcedStyle: have_no
Capybara/ClickLinkOrButtonStyle:
EnforcedStyle: link_or_button
RSpec/ExampleLength:
inherit_mode:
merge:
- Exclude
CountAsOne:
- "array"
- "hash"
- "heredoc"
- 'array'
- 'hash'
- 'heredoc'
Max: 25
Exclude:
- "spec/system/**/*.rb"
@@ -444,7 +444,7 @@ FactoryBot/ExcessiveCreateList:
Zammad/PreferNegatedIfOverUnless:
Exclude:
- "bin/rspec"
- 'bin/rspec'
Zammad/ExistsDateTimePrecision:
Include:
@@ -456,16 +456,16 @@ Zammad/ExistsResetColumnInformation:
- "db/migrate/*.rb"
- "**/db/addon/**/*.rb"
Exclude:
- "db/migrate/201*_*.rb"
- "db/migrate/2020*_*.rb"
- 'db/migrate/201*_*.rb'
- 'db/migrate/2020*_*.rb'
Zammad/MigrationSchedulerLastRun:
Include:
- "db/migrate/*.rb"
- "**/db/addon/**/*.rb"
Exclude:
- "db/migrate/201*_*.rb"
- "db/migrate/2020*_*.rb"
- 'db/migrate/201*_*.rb'
- 'db/migrate/2020*_*.rb'
Zammad/DetectTranslatableString:
Enabled: true
@@ -496,97 +496,4 @@ Zammad/ExistsDbStrategy:
Zammad/EnsuresTimeZoneAtSpecTopLevel:
Enabled: true
Include:
- "spec/system/**/*.rb"
Zammad/GraphqlAuthorizeAllowPublicAccess:
Enabled: true
Include:
- "app/graphql/gql/**/*.rb"
Zammad/GraphqlAuthorizeDisallowed:
Enabled: true
Include:
- "app/graphql/gql/**/*.rb"
Zammad/GraphqlAuthorizeRequiresPermission:
Enabled: true
Include:
- "app/graphql/gql/**/*.rb"
Zammad/GraphqlAuthorizedRequiresEnabledSetting:
Enabled: true
Include:
- "app/graphql/gql/**/*.rb"
Zammad/GraphqlCsrfSkipVerification:
Enabled: true
Include:
- "app/graphql/gql/**/*.rb"
Zammad/GraphqlForbidSensitiveFields:
Enabled: true
Include:
- "app/graphql/gql/types/**/*.rb"
- "app/graphql/gql/mutations/**/*.rb"
- "app/graphql/gql/queries/**/*.rb"
- "app/graphql/gql/subscriptions/**/*.rb"
Exclude:
- "app/graphql/gql/types/input/**/*.rb"
Zammad/MigrationEnsureIntegerPrimaryKey:
Enabled: true
Include:
- "db/migrate/*.rb"
Zammad/MigrationEnsureIntegerForeignKey:
Enabled: true
Include:
- "db/migrate/*.rb"
Capybara/RSpec/NegationMatcherAfterVisit:
Description: "Allow to use negated matcher after visit"
Enabled: false
RSpec/IncludeExamples:
Description: "Allow to use both it_behaves_like and include_examples"
Enabled: false
Rails/FindByOrAssignmentMemoization:
Description: "Allow to use ||= for memoization of find_by returns"
Enabled: false
# Looks like this cop is currently broken.
# Allowing shorthands in one-line blocks would be fine.
# But as of now it mis-detects chained blocks as multiliners.
Style/ItBlockParameter:
Description: "Allow to use it shorthand in blocks"
EnforcedStyle: only_numbered_parameters
Style/OneClassPerFile:
Description: "Allow to have multiple classes in one file"
Enabled: false
# There is a warning in the cop about possible performance issues,
# see https://docs.rubocop.org/rubocop-rails/cops_rails.html#safety-railspluckinwhere.
Rails/PluckInWhere:
Description: "Identifies places where pluck is used in where query methods and can be replaced with select."
Enabled: false
Rails/Exit:
Exclude:
- "lib/tasks/**/*"
RSpec/Output:
Enabled: false
Style/StderrPuts:
Exclude:
- "lib/tasks/**/*"
Lint/MissingSuper:
Description: "Checks for missing super calls in overridden methods. Skips Service classes."
Exclude:
- "app/services/**/*"
Rails/StrongParametersExpect:
Enabled: false
- 'spec/system/**/*.rb'

View File

@@ -11,27 +11,14 @@ require_relative 'cop/zammad/exists_reset_column_information'
require_relative 'cop/zammad/faker_unique'
require_relative 'cop/zammad/forbid_def_send'
require_relative 'cop/zammad/forbid_default_scope'
require_relative 'cop/zammad/forbid_loofah_document'
require_relative 'cop/zammad/forbid_loofah_fragment'
require_relative 'cop/zammad/forbid_rand'
require_relative 'cop/zammad/forbid_redis_client'
require_relative 'cop/zammad/forbid_translatable_marker'
require_relative 'cop/zammad/forbid_unprocessable_entity'
require_relative 'cop/zammad/graphql_authorize_allow_public_access'
require_relative 'cop/zammad/graphql_authorize_requires_permission'
require_relative 'cop/zammad/graphql_authorize_disallowed'
require_relative 'cop/zammad/graphql_authorized_requires_enabled_setting'
require_relative 'cop/zammad/graphql_csrf_skip_verification'
require_relative 'cop/zammad/graphql_forbid_sensitive_fields'
require_relative 'cop/zammad/migration_scheduler_last_run'
require_relative 'cop/zammad/no_to_sym_on_string'
require_relative 'cop/zammad/prefer_models_all'
require_relative 'cop/zammad/prefer_negated_if_over_unless'
require_relative 'cop/zammad/timezone_default'
require_relative 'cop/zammad/to_forbid_over_not_to_permit'
require_relative 'cop/zammad/trigger_from_commit_hooks'
require_relative 'cop/zammad/update_copyright'
require_relative 'cop/zammad/ensures_time_zone_at_spec_top_level'
require_relative 'cop/zammad/migration_ensure_integer_primary_key'
require_relative 'cop/zammad/migration_ensure_integer_foreign_key'
require_relative 'cop/zammad/forbid_calling_service_directly'

View File

@@ -10,6 +10,7 @@ RSpec/ContextWording:
- 'spec/db/migrate/issue_2345_es_attachment_max_size_in_mb_setting_lower_default_spec.rb'
- 'spec/db/migrate/issue_2368_add_indices_to_histories_and_tickets_spec.rb'
- 'spec/db/migrate/issue_2541_fix_notification_email_without_body_spec.rb'
- 'spec/jobs/communicate_twitter_job_spec.rb'
- 'spec/jobs/concerns/has_active_job_lock_spec.rb'
- 'spec/jobs/concerns/has_collection_update_spec.rb'
- 'spec/jobs/imap_authentication_migration_cleanup_job_spec.rb'
@@ -54,9 +55,11 @@ RSpec/ContextWording:
- 'spec/models/application_model/can_assets_examples.rb'
- 'spec/models/application_model/checks_import_examples.rb'
- 'spec/models/calendar_spec.rb'
- 'spec/models/channel/driver/twitter_spec.rb'
- 'spec/models/channel/email_parser_spec.rb'
- 'spec/models/channel/filter/follow_up_merged_spec.rb'
- 'spec/models/channel/filter/follow_up_possible_check_spec.rb'
- 'spec/models/channel/filter/match/email_regex_spec.rb'
- 'spec/models/channel/filter/out_of_office_check_spec.rb'
- 'spec/models/concerns/can_csv_import_examples.rb'
- 'spec/models/concerns/checks_kb_client_notification_examples.rb'
@@ -123,6 +126,7 @@ RSpec/ContextWording:
- 'spec/system/basic/assets_spec.rb'
- 'spec/system/basic/richtext_spec.rb'
- 'spec/system/channels/email_spec.rb'
- 'spec/system/channels/twitter_spec.rb'
- 'spec/system/cti_spec.rb'
- 'spec/system/dashboard_spec.rb'
- 'spec/system/keyboard_shortcuts_spec.rb'
@@ -155,6 +159,7 @@ RSpec/ContextWording:
- 'spec/system/system/integration/clearbit_spec.rb'
- 'spec/system/system/integration/icinga_spec.rb'
- 'spec/system/system/integration/sipgate_spec.rb'
- 'spec/system/system/integration/slack_spec.rb'
- 'spec/system/system/integration/smime_spec.rb'
- 'spec/system/system/object_manager_spec.rb'
- 'spec/system/ticket/create_spec.rb'
@@ -162,10 +167,6 @@ RSpec/ContextWording:
- 'spec/system/ticket/shared_draft_zoom_spec.rb'
- 'spec/system/ticket/update/full_quote_header_spec.rb'
- 'spec/system/ticket/view_spec.rb'
- 'spec/system/ticket/zoom/article_actions_spec.rb'
- 'spec/system/ticket/zoom/article_creation_spec.rb'
- 'spec/system/ticket/zoom/object_attributes_spec.rb'
- 'spec/system/ticket/zoom/taskbar_spec.rb'
- 'spec/system/ticket/zoom_spec.rb'
- 'spec/system/user/profile_spec.rb'
@@ -221,7 +222,6 @@ RSpec/SpecFilePathFormat:
- 'spec/jobs/issue_*_spec.rb'
- 'spec/lib/import/base_factory_spec.rb'
- 'spec/models/knowledge_base/answer/translation/search_with_attachment_spec.rb'
- 'spec/models/trigger/base_spec.rb'
- 'spec/models/trigger/sms_spec.rb'
# This was split up into RSpec/SpecFilePathSuffix and RSpec/SpecFilePathFormat,
@@ -235,11 +235,13 @@ RSpec/InstanceVariable:
RSpec/LetSetup:
Exclude:
- 'spec/jobs/communicate_twitter_job_spec.rb'
- 'spec/jobs/ticket_online_notification_seen_job_spec.rb'
- 'spec/lib/external_credential/google_spec.rb'
- 'spec/lib/external_credential/microsoft365_spec.rb'
- 'spec/lib/secure_mailing/smime_spec.rb'
- 'spec/lib/sessions/backend/ticket_overview_list_spec.rb'
- 'spec/models/channel/driver/twitter_spec.rb'
- 'spec/models/channel/email_parser_spec.rb'
- 'spec/models/cti/caller_id_spec.rb'
- 'spec/models/cti/driver/base_spec.rb'
@@ -301,6 +303,7 @@ RSpec/MessageSpies:
- 'spec/lib/sequencer/unit/import/common/object_attribute/sanitized_name_spec.rb'
- 'spec/lib/sequencer/unit/import/zendesk/sub_sequence/base_examples.rb'
- 'spec/lib/sequencer/unit/import/zendesk/ticket/comment/attachment/request_spec.rb'
- 'spec/models/channel/driver/twitter_spec.rb'
- 'spec/models/channel/email_parser_spec.rb'
- 'spec/models/concerns/has_groups_examples.rb'
- 'spec/models/concerns/has_history_examples.rb'
@@ -323,7 +326,9 @@ RSpec/MultipleExpectations:
- 'spec/db/migrate/rename_locale_on_users_spec.rb'
- 'spec/integration/ldap_spec.rb'
- 'spec/integration/otrs_spec.rb'
- 'spec/jobs/communicate_twitter_job_spec.rb'
- 'spec/jobs/concerns/has_active_job_lock_spec.rb'
- 'spec/jobs/migrate_ldap_samaccountname_to_uid_job/ldap_spec.rb'
- 'spec/jobs/search_index_job_spec.rb'
- 'spec/jobs/ticket_user_ticket_counter_job_spec.rb'
- 'spec/lib/core_ext/string_spec.rb'
@@ -362,6 +367,7 @@ RSpec/MultipleExpectations:
- 'spec/lib/user_info_spec.rb'
- 'spec/models/calendar_spec.rb'
- 'spec/models/channel/driver/sms/twilio_spec.rb'
- 'spec/models/channel/driver/twitter_spec.rb'
- 'spec/models/channel/email_parser_spec.rb'
- 'spec/models/concerns/has_groups_examples.rb'
- 'spec/models/concerns/has_history_examples.rb'
@@ -388,7 +394,6 @@ RSpec/MultipleExpectations:
- 'spec/models/ticket/overviews_spec.rb'
- 'spec/models/ticket_spec.rb'
- 'spec/models/translation_spec.rb'
- 'spec/models/trigger/base_spec.rb'
- 'spec/models/trigger_spec.rb'
- 'spec/models/type_lookup_spec.rb'
- 'spec/models/user_spec.rb'
@@ -438,6 +443,7 @@ RSpec/MultipleExpectations:
RSpec/NestedGroups:
Exclude:
- 'spec/lib/secure_mailing/smime_spec.rb'
- 'spec/models/channel/driver/twitter_spec.rb'
- 'spec/models/channel/email_parser_spec.rb'
- 'spec/models/job_spec.rb'
- 'spec/models/store_spec.rb'
@@ -448,6 +454,8 @@ RSpec/NestedGroups:
RSpec/VerifiedDoubles:
Exclude:
- 'spec/db/migrate/issue_2460_fix_corrupted_twitter_ids_spec.rb'
- 'spec/jobs/communicate_twitter_job_spec.rb'
- 'spec/lib/external_sync_spec.rb'
- 'spec/lib/ldap_spec.rb'
- 'spec/lib/sequencer/sequence/import/ldap/users_spec.rb'

View File

@@ -41,6 +41,7 @@ Metrics/AbcSize:
- 'app/jobs/communicate_facebook_job.rb'
- 'app/jobs/communicate_sms_job.rb'
- 'app/jobs/communicate_telegram_job.rb'
- 'app/jobs/communicate_twitter_job.rb'
- 'app/jobs/ticket_article_communicate_email_job.rb'
- 'app/models/activity_stream/assets.rb'
- 'app/models/application_model/can_associations.rb'
@@ -50,6 +51,8 @@ Metrics/AbcSize:
- 'app/models/avatar.rb'
- 'app/models/calendar.rb'
- 'app/models/channel.rb'
- 'app/models/channel/assets.rb'
- 'app/models/channel/driver/twitter.rb'
- 'app/models/channel/email_build.rb'
- 'app/models/channel/email_parser.rb'
- 'app/models/channel/filter/auto_response_check.rb'
@@ -94,6 +97,7 @@ Metrics/AbcSize:
- 'app/models/ticket/sets_last_owner_update_time.rb'
- 'app/models/transaction/notification.rb'
- 'app/models/transaction/signature_detection.rb'
- 'app/models/transaction/slack.rb'
- 'app/models/translation.rb'
- 'app/models/trigger/assets.rb'
- 'app/models/user.rb'
@@ -120,6 +124,7 @@ Metrics/AbcSize:
- 'lib/external_credential/facebook.rb'
- 'lib/external_credential/google.rb'
- 'lib/external_credential/microsoft_base.rb'
- 'lib/external_credential/twitter.rb'
- 'lib/facebook.rb'
- 'lib/fill_db.rb'
- 'lib/import/otrs.rb'
@@ -150,6 +155,7 @@ Metrics/AbcSize:
- 'lib/stats/ticket_waiting_time.rb'
- 'lib/telegram_helper.rb'
- 'lib/transaction_dispatcher.rb'
- 'lib/twitter_sync.rb'
- 'lib/user_agent.rb'
- 'lib/websocket_server.rb'
- 'spec/**/*'
@@ -158,6 +164,7 @@ Metrics/AbcSize:
Metrics/BlockLength:
Exclude:
- 'Gemfile'
- 'config/initializers/mariadb_json_columns.rb'
- 'app/models/ticket/can_selector.rb'
- 'app/controllers/application_controller/authenticates.rb'
- 'app/controllers/reports_controller.rb'
@@ -177,7 +184,6 @@ Metrics/BlockLength:
- 'app/models/concerns/can_be_published.rb'
- 'app/models/concerns/can_clone_attachments.rb'
- 'app/models/concerns/can_csv_import.rb'
- 'app/models/concerns/has_audit_logs.rb'
- 'app/models/concerns/has_groups.rb'
- 'app/models/concerns/has_history.rb'
- 'app/models/concerns/has_rich_text.rb'
@@ -191,6 +197,7 @@ Metrics/BlockLength:
- 'app/models/ticket/search.rb'
- 'lib/selector/search_index.rb'
- 'app/models/transaction/notification.rb'
- 'app/models/transaction/slack.rb'
- 'app/models/translation.rb'
- 'app/models/user/search.rb'
- 'config/environments/development.rb'
@@ -220,6 +227,7 @@ Metrics/BlockLength:
- 'lib/tasks/cache/measure_io_performance.rake'
- 'lib/tasks/zammad/search_index_es.rake'
- 'lib/transaction_dispatcher.rb'
- 'lib/twitter_sync.rb'
- 'script/websocket-server.rb'
- 'spec/**/*'
- 'test/**/*'
@@ -231,6 +239,8 @@ Metrics/BlockNesting:
- 'app/models/concerns/has_history.rb'
- 'app/models/object_manager/attribute.rb'
- 'lib/notification_factory/mailer.rb'
- 'lib/twitter_sync.rb'
- 'test/browser_test_helper.rb'
Metrics/CyclomaticComplexity:
Max: 12
@@ -251,6 +261,7 @@ Metrics/CyclomaticComplexity:
- 'app/controllers/users_controller.rb'
- 'app/jobs/communicate_sms_job.rb'
- 'app/jobs/communicate_telegram_job.rb'
- 'app/jobs/communicate_twitter_job.rb'
- 'app/jobs/ticket_article_communicate_email_job.rb'
- 'app/models/application_model/can_associations.rb'
- 'app/models/application_model/can_cleanup_param.rb'
@@ -259,10 +270,13 @@ Metrics/CyclomaticComplexity:
- 'app/models/avatar.rb'
- 'app/models/calendar.rb'
- 'app/models/channel.rb'
- 'app/models/channel/assets.rb'
- 'app/models/channel/driver/twitter.rb'
- 'app/models/channel/email_build.rb'
- 'app/models/channel/email_parser.rb'
- 'app/models/channel/filter/auto_response_check.rb'
- 'app/models/channel/filter/bounce_delivery_permanent_failed.rb'
- 'app/models/channel/filter/database.rb'
- 'app/models/channel/filter/follow_up_check.rb'
- 'app/models/channel/filter/identify_sender.rb'
- 'app/models/channel/filter/monitoring_base.rb'
@@ -285,6 +299,7 @@ Metrics/CyclomaticComplexity:
- 'app/models/ticket/search.rb'
- 'app/models/ticket/sets_last_owner_update_time.rb'
- 'app/models/transaction/notification.rb'
- 'app/models/transaction/slack.rb'
- 'app/models/user.rb'
- 'app/models/user/assets.rb'
- 'app/models/user/search.rb'
@@ -296,6 +311,7 @@ Metrics/CyclomaticComplexity:
- 'lib/excel_sheet.rb'
- 'lib/external_credential/google.rb'
- 'lib/external_credential/microsoft_base.rb'
- 'lib/external_credential/twitter.rb'
- 'lib/facebook.rb'
- 'lib/fill_db.rb'
- 'lib/models.rb'
@@ -318,6 +334,7 @@ Metrics/CyclomaticComplexity:
- 'lib/stats/ticket_channel_distribution.rb'
- 'lib/telegram_helper.rb'
- 'lib/transaction_dispatcher.rb'
- 'lib/twitter_sync.rb'
- 'lib/user_agent.rb'
- 'test/**/*'
@@ -325,7 +342,6 @@ Metrics/ModuleLength:
Exclude:
- 'app/models/ticket/can_selector.rb'
- 'app/controllers/application_controller/authenticates.rb'
- 'app/controllers/application_controller/handles_errors.rb'
- 'app/controllers/application_controller/renders_models.rb'
- 'app/models/application_model/can_assets.rb'
- 'app/models/application_model/can_associations.rb'
@@ -335,7 +351,6 @@ Metrics/ModuleLength:
- 'app/models/concerns/can_search.rb'
- 'app/models/concerns/can_be_published.rb'
- 'app/models/concerns/can_csv_import.rb'
- 'app/models/concerns/has_audit_logs.rb'
- 'app/models/concerns/has_groups.rb'
- 'app/models/concerns/has_history.rb'
- 'app/models/concerns/has_rich_text.rb'
@@ -373,6 +388,7 @@ Metrics/PerceivedComplexity:
- 'app/controllers/users_controller.rb'
- 'app/jobs/communicate_sms_job.rb'
- 'app/jobs/communicate_telegram_job.rb'
- 'app/jobs/communicate_twitter_job.rb'
- 'app/jobs/ticket_article_communicate_email_job.rb'
- 'app/models/application_model/can_associations.rb'
- 'app/models/application_model/can_cleanup_param.rb'
@@ -381,10 +397,13 @@ Metrics/PerceivedComplexity:
- 'app/models/avatar.rb'
- 'app/models/calendar.rb'
- 'app/models/channel.rb'
- 'app/models/channel/assets.rb'
- 'app/models/channel/driver/twitter.rb'
- 'app/models/channel/email_build.rb'
- 'app/models/channel/email_parser.rb'
- 'app/models/channel/filter/auto_response_check.rb'
- 'app/models/channel/filter/bounce_delivery_permanent_failed.rb'
- 'app/models/channel/filter/database.rb'
- 'app/models/channel/filter/follow_up_check.rb'
- 'app/models/channel/filter/identify_sender.rb'
- 'app/models/channel/filter/monitoring_base.rb'
@@ -409,6 +428,7 @@ Metrics/PerceivedComplexity:
- 'app/models/ticket/search.rb'
- 'app/models/ticket/sets_last_owner_update_time.rb'
- 'app/models/transaction/notification.rb'
- 'app/models/transaction/slack.rb'
- 'app/models/user.rb'
- 'app/models/user/assets.rb'
- 'app/models/user/search.rb'
@@ -421,6 +441,7 @@ Metrics/PerceivedComplexity:
- 'lib/excel_sheet.rb'
- 'lib/external_credential/google.rb'
- 'lib/external_credential/microsoft_base.rb'
- 'lib/external_credential/twitter.rb'
- 'lib/facebook.rb'
- 'lib/fill_db.rb'
- 'lib/models.rb'
@@ -441,7 +462,9 @@ Metrics/PerceivedComplexity:
- 'lib/stats/ticket_channel_distribution.rb'
- 'lib/telegram_helper.rb'
- 'lib/transaction_dispatcher.rb'
- 'lib/twitter_sync.rb'
- 'lib/user_agent.rb'
- 'test/browser_test_helper.rb'
Rails/CreateTableWithTimestamps:
Exclude:
@@ -472,6 +495,7 @@ Style/OptionalBooleanParameter:
- 'app/models/channel/driver/sms/message_bird.rb'
- 'app/models/channel/driver/sms/twilio.rb'
- 'app/models/channel/driver/telegram.rb'
- 'app/models/channel/driver/twitter.rb'
- 'app/models/channel/driver/whatsapp.rb'
- 'app/models/channel/email_build.rb'
- 'app/models/channel/email_parser.rb'
@@ -492,14 +516,17 @@ Style/OptionalBooleanParameter:
- 'lib/external_credential/google.rb'
- 'lib/external_credential/microsoft_base.rb'
- 'lib/external_credential/exchange.rb'
- 'lib/external_credential/twitter.rb'
- 'lib/html_sanitizer.rb'
- 'lib/models.rb'
- 'lib/sessions/backend/ticket_overview_list.rb'
- 'lib/sessions/node.rb'
- 'spec/support/system_init_done.rb'
- 'test/browser_test_helper.rb'
Style/ReturnNilInPredicateMethodDefinition:
Exclude:
- app/models/application_model/can_lookup_search_index_attributes.rb
- app/models/channel/filter/import_archive.rb
- app/models/core_workflow/attributes.rb
- app/models/core_workflow/condition.rb
@@ -520,4 +547,4 @@ Style/ReturnNilInPredicateMethodDefinition:
- lib/import/otrs/diff.rb
- lib/search_index_backend.rb
- lib/sequencer/unit/import/kayako/sub_sequence/generic.rb
- lib/sessions/event/base.rb
- lib/sessions/event/base.rb

View File

@@ -22,7 +22,7 @@ Dir.glob("#{__dir__}/todo*.yml").each do |f|
end
end
if obsolete_excludes.any?
if obsolete_excludes.count.positive?
puts 'Obsolete rubocop todo*.yml entries found for these files:'
obsolete_excludes.sort.uniq.each do |file|
puts " - #{file}"

View File

@@ -1,3 +0,0 @@
---
gitlab:
enabled: true

View File

@@ -1,72 +0,0 @@
---
name: "Update Zammad dependencies via updatecli"
sources:
heroku-buildpack-nodejs:
name: heroku-buildpack-nodejs
kind: gittag
spec:
url: "https://github.com/heroku/heroku-buildpack-nodejs.git"
versionfilter:
kind: "semver"
heroku-buildpack-ruby:
name: heroku-buildpack-ruby
kind: gitbranch
spec:
url: "https://github.com/pkgr/heroku-buildpack-ruby.git"
versionfilter:
kind: "regex"
pattern: 'v\d{3}-\d+'
conditions: {}
targets:
buildpacks-nodejs:
name: buildpacks-nodejs
kind: file
sourceid: heroku-buildpack-nodejs
# {{ if .gitlab.enabled }}
scmid: zammad
# {{ end }}
spec:
file: .buildpacks
matchpattern: 'https://github.com/heroku/heroku-buildpack-nodejs.git#.*'
replacepattern: 'https://github.com/heroku/heroku-buildpack-nodejs.git#{{ source `heroku-buildpack-nodejs` }}'
buildpacks-ruby:
name: buildpacks-ruby
kind: file
sourceid: heroku-buildpack-ruby
# {{ if .gitlab.enabled }}
scmid: zammad
# {{ end }}
spec:
file: .buildpacks
matchpattern: 'https://github.com/pkgr/heroku-buildpack-ruby.git#.*'
replacepattern: 'https://github.com/pkgr/heroku-buildpack-ruby.git#{{ source `heroku-buildpack-ruby` }}'
# {{ if .gitlab.enabled }}
scms:
zammad:
kind: "gitlab"
spec:
url: '{{ requiredEnv "CI_SERVER_URL" }}'
owner: "zammad"
repository: "zammad"
token: '{{ requiredEnv "RENOVATE_TOKEN" }}'
username: 'renovate'
branch: "develop"
workingbranchprefix: "private/updatecli"
workingbranchseparator: "/"
actions:
gitlab:
kind: gitlab/mergerequest
scmid: zammad
spec:
automerge: false
title: "Maintenance: Update Zammad dependencies via updatecli"
assignees:
- 115 # fl
labels:
- "dependencies"
# {{ end }}

View File

@@ -1,80 +0,0 @@
name: "zammad-devcontainer"
services:
devcontainer:
"image": "ghcr.io/rails/devcontainer/images/ruby:3.4.9"
environment:
MEMCACHE_SERVERS: memcached:11211
# Don't specify the postgresql database here. This will get merged with an existing database.yml.
DATABASE_URL: postgres://zammad:zammad@postgresql:5432
REDIS_URL: redis://redis:6379
# Performance / DX / tests tuning
CI_SKIP_ASSETS_PRECOMPILE: 'true'
CI_SKIP_DB_RESET: 'true'
RAILS_SERVE_STATIC_FILES: 'true'
TZ: 'Europe/London'
Z_LOCALES: en-us:en-gb:de-de
ES_URL: http://elasticsearch:9200
ES_INDEX: estest.local_zammad
VITE_RUBY_HOST: '127.0.0.1'
BETA_UI_FEEDBACK_API_HOST: 'http://localhost:3000'
depends_on:
- memcached
- postgresql
- redis
volumes:
- ../../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
elasticsearch:
image: elasticsearch:9.5.3
environment:
- "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
- discovery.type=single-node
- xpack.security.enabled=false
- http.cors.enabled=true
- http.cors.allow-origin=/https:\/\/app.elasticvue.com/
# Uncomment and change the following settings if you need to allow larger payloads, e.g. for debugging or testing.
# Max length of 100MB translates to ~40MB for allowed attachments, due to base64 encoding overhead.
# Indexing pressure limit of 10% of heap memory is enough for ~50MB attachments (see a ES_JAVA_OPTS above).
# Keep in mind to keep these limits aligned with the following Zammad settings:
# `es_attachment_max_size_in_mb=10` and `es_total_max_size_in_mb=300`.
# - http.max_content_length=100mb
# - indexing_pressure.memory.limit=10%
# - indexing_pressure.memory.coordinating.limit=10%
restart: unless-stopped
ports:
# Expose the ES service for direct access if needed, but avoid conflicts with host services.
- 127.0.0.1:19200:9200
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
memcached:
command: memcached -m 256M
image: memcached:1.6.45-alpine
restart: unless-stopped
postgresql:
environment:
POSTGRES_DB: zammad_development
POSTGRES_USER: zammad
POSTGRES_PASSWORD: zammad
image: postgres:17.5-alpine
restart: unless-stopped
volumes:
- postgresql-data:/var/lib/postgresql/data
redis:
image: redis:7.0
restart: unless-stopped
volumes:
elasticsearch-data:
driver: local
postgresql-data:
driver: local

View File

@@ -1,44 +0,0 @@
{
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:latest": {
"version": "1.0.5",
"resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a",
"integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a"
},
"ghcr.io/devcontainers-extra/features/apt-get-packages": {
"version": "1.0.8",
"resolved": "ghcr.io/devcontainers-extra/features/apt-get-packages@sha256:776fa11c30c50fa91d5e1bf050444b0f130827457109c349db92e8e126b750ec",
"integrity": "sha256:776fa11c30c50fa91d5e1bf050444b0f130827457109c349db92e8e126b750ec"
},
"ghcr.io/devcontainers-extra/features/shellcheck:1": {
"version": "1.1.0",
"resolved": "ghcr.io/devcontainers-extra/features/shellcheck@sha256:8e6d050e4ead6a0350dbe4c70611e351f32cb61a8cc5cb2b39da0cf29ff3bed5",
"integrity": "sha256:8e6d050e4ead6a0350dbe4c70611e351f32cb61a8cc5cb2b39da0cf29ff3bed5"
},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"version": "1.10.0",
"resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e",
"integrity": "sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e"
},
"ghcr.io/devcontainers/features/go:1": {
"version": "1.3.4",
"resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032",
"integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032"
},
"ghcr.io/devcontainers/features/node:2": {
"version": "2.1.0",
"resolved": "ghcr.io/devcontainers/features/node@sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857",
"integrity": "sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857"
},
"ghcr.io/rails/devcontainer/features/postgres-client": {
"version": "1.2.0",
"resolved": "ghcr.io/rails/devcontainer/features/postgres-client@sha256:7e6b118646d6e0d82a9ec06e81ad1b5406f7042f0279d35fff3a7a612be7a050",
"integrity": "sha256:7e6b118646d6e0d82a9ec06e81ad1b5406f7042f0279d35fff3a7a612be7a050"
},
"ghcr.io/rapidsai/devcontainers/features/gitlab-cli:latest": {
"version": "26.8.2",
"resolved": "ghcr.io/rapidsai/devcontainers/features/gitlab-cli@sha256:45b15ff1035cb7c2ac657ee069f9c548744dd6d70311f7abbd3aea12c5ec0c92",
"integrity": "sha256:45b15ff1035cb7c2ac657ee069f9c548744dd6d70311f7abbd3aea12c5ec0c92"
}
}
}

View File

@@ -1,50 +0,0 @@
// For format details, see https://containers.dev/implementors/json_reference/.
// For config options, see the README at: https://github.com/devcontainers/templates/tree/main/src/ruby
{
"name": "Zammad (default)",
"dockerComposeFile": "compose.yaml",
"service": "devcontainer",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"moby": false
},
"ghcr.io/devcontainers-extra/features/shellcheck:1": {},
"ghcr.io/devcontainers/features/node:2": {
"installYarnUsingApt": "false"
},
"ghcr.io/rails/devcontainer/features/postgres-client": {},
"ghcr.io/devcontainers-extra/features/apt-get-packages": {
"packages": "curl gnupg libimlib2-dev"
},
"ghcr.io/devcontainers/features/go:1": {},
"ghcr.io/rapidsai/devcontainers/features/gitlab-cli:latest": {},
"ghcr.io/anthropics/devcontainer-features/claude-code:latest": {},
"../features/zammad-deps": {},
"../features/add-bin-path": {
"addBinPath": "/workspaces/${localWorkspaceFolderBasename}/bin"
}
},
"mounts": [
{
// Avoid conflicts with host-installed node_modules with a different platform/architecture.
"source": "node-modules",
"target": "/workspaces/${localWorkspaceFolderBasename}/node_modules",
"type": "volume"
},
{
"source": "pnpm-store",
"target": "/workspaces/${localWorkspaceFolderBasename}/.pnpm-store",
"type": "volume"
}
],
"containerEnv": {},
"forwardPorts": [
3000,
3001,
3036,
6042
],
"postCreateCommand": "/workspaces/${localWorkspaceFolderBasename}/.devcontainer/post_create.sh",
"postStartCommand": "git config --global --add safe.directory /workspaces/${localWorkspaceFolderBasename}"
}

View File

@@ -1,14 +0,0 @@
{
"id": "add-bin-path",
"version": "1.0.0",
"name": "Add Bin Path",
"description": "Adds a custom bin path to the container environment.",
"options": {
"addBinPath": {
"type": "string",
"default": "",
"description": "The path to add to the container's PATH."
}
},
"privileged": true
}

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env sh
echo "export PATH=$ADDBINPATH:\$PATH" > /etc/profile.d/add_bin_path.sh

View File

@@ -1,35 +0,0 @@
{
"id": "zammad-deps",
"version": "1.0.0",
"name": "Zammad Dependency Installer",
"description": "Installs dependencies required for Zammad.",
"customizations": {
"vscode": {
"extensions": [
"andyyaldoo.vscode-json",
"anthropic.claude-code",
"bradlc.vscode-tailwindcss",
"dariofuzinato.vue-peek",
"davidanson.vscode-markdownlint",
"dbaeumer.vscode-eslint",
"dotjoshjohnson.xml",
"eamodio.gitlens",
"ezforo.copy-relative-path-and-line-numbers",
"jock.svg",
"kaiwood.endwise",
"mde.select-highlight-minimap",
"oxc.oxc-vscode",
"redhat.vscode-yaml",
"shardulm94.trailing-spaces",
"shopify.ruby-lsp",
"slb235.vscode-coffeelint",
"streetsidesoftware.code-spell-checker",
"timonwong.shellcheck",
"vitest.explorer",
"vue.volar"
]
}
},
"privileged": true,
"installsAfter": ["ghcr.io/devcontainers/features/go"]
}

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env sh
export PATH="/usr/local/go/bin:$PATH"
go install github.com/ddollar/forego@master

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env sh
env
# Ensure proper permissions on root-mounted volumes.
sudo chown -R "${USER}" node_modules .pnpm-store
export SHELL=/bin/bash
pnpm setup
export PNPM_HOME="${HOME}/.local/share/pnpm"
export PATH="${PNPM_HOME}/bin:${PATH}"
pnpm config set store-dir "$(pwd)/.pnpm-store" --global
bin/setup --skip-server
echo "== Initialize CodeGraph index =="
pnpm run codegraph:init
echo "== Precompile Ruby cache =="
bundle exec bootsnap precompile --gemfile app/ lib/
echo "== Configure Elasticsearch URL =="
bundle exec rails r "Setting.set('es_url', 'http://elasticsearch:9200')"
echo "== Run auto_wizard setup =="
bundle exec rails zammad:setup:auto_wizard
echo "== Rebuild search index =="
bundle exec rails zammad:searchindex:rebuild
echo "== Precompile frontend assets =="
bundle exec rails assets:precompile
echo "== Prepare test database =="
RAILS_ENV='test' bundle exec rails db:drop db:create zammad:ci:test:prepare || exit 1

View File

@@ -1,89 +0,0 @@
# Zammad devcontainer - with LDAP
A devcontainer variant that adds an OpenLDAP sidecar pre-configured for
Zammad LDAP integration testing.
## Ports
| Service | Host port | Protocol |
|---------|-----------|----------|
| LDAP | 1389 | plain / STARTTLS |
| LDAPS | 1636 | TLS |
## Pre-seeded directory
Base DN: `dc=foo,dc=example,dc=com`
Admin DN: `cn=admin,dc=foo,dc=example,dc=com` - password `test`
### Users (`uid` / password)
| uid | Name | Password |
|-----------|---------------|-----------|
| nb | Nicole Braun | testnb |
| ab | Albert Braun | testab |
| bb | Berta Braun | testbb |
| fb | Frida Braun | testfb |
| lb | Lena Braun | testlb |
| eb | Elke Braun | testeb |
| customer1 | Customer 1 | testc1 |
| … | … | … |
| customer8 | Customer 8 | testc8 |
### Groups (`ou=groups,dc=foo,dc=example,dc=com`)
`admin`, `1st level`, `2nd level`, `3rd level`, `4th level`, `sales`
The post-create script maps `cn=admin,ou=groups,…` → Zammad role Admin and
`cn=1st level,ou=groups,…` → Zammad role Agent.
## Configuration
Edit the `environment:` block in `.devcontainer/with-ldap/compose.yaml` and
rebuild the devcontainer (VSCode will prompt you automatically when the file
changes).
Key variables:
| Variable | Default | Description |
|------------------------|------------------------------|-------------|
| `LDAP_BASE_DN` | `dc=foo,dc=example,dc=com` | Directory base DN |
| `LDAP_ADMIN_DN` | `cn=admin,<LDAP_BASE_DN>` | Admin bind DN (derived if unset) |
| `LDAP_ADMIN_PASSWORD` | `test` | Admin password |
| `LDAP_DISALLOW_BIND_ANON` | `false` | Reject anonymous binds when `true` |
> **Note:** `.devcontainer/with-ldap/scripts/post_create.sh` also contains the
> base DN, admin DN, and admin password as hardcoded strings (in the
> `LdapSource.create!` call and `group_role_map` keys). If you change any of
> the values above in `compose.yaml`, update the corresponding values in
> `post_create.sh` as well, then rebuild the devcontainer.
## TLS certificates
A self-signed CA and server certificate are generated automatically on first
container startup into `ldap/certs/` (gitignored, only a `.gitkeep` is
committed). The post-create script imports the CA from that path into Zammad's
SSL store.
To use your own certificates, place `ca.crt`, `ldap.crt`, and `ldap.key` in
`ldap/certs/` before starting the container. The entrypoint skips generation
when `ldap.key` is already present, and the post-create script imports
`ca.crt` from that path automatically — no manual steps needed.
If you need to swap certificates on an already-running devcontainer, import
the new CA manually:
```sh
bundle exec rails r "SSLCertificate.create!(certificate: Rails.root.join('.devcontainer/with-ldap/ldap/certs/ca.crt').read)"
```
## Customising the directory seed
Edit `.devcontainer/with-ldap/ldap/bootstrap/ldif/zammad.ldif` before the
first container start. The LDIF is loaded only once (on initialisation). To
re-seed, remove the container and restart:
```sh
docker compose -f .devcontainer/with-ldap/compose.yaml rm -sv ldap
docker compose -f .devcontainer/with-ldap/compose.yaml up -d ldap
```

View File

@@ -1,18 +0,0 @@
name: "zammad-devcontainer"
include: [../default/compose.yaml]
services:
ldap:
build: ./ldap
environment:
LDAP_BASE_DN: dc=foo,dc=example,dc=com
LDAP_ADMIN_DN: cn=admin,dc=foo,dc=example,dc=com
LDAP_ADMIN_PASSWORD: test
LDAP_DISALLOW_BIND_ANON: "false"
volumes:
- ./ldap/certs:/certs
ports:
- 127.0.0.1:1389:389 # LDAP (no TLS/STARTTLS)
- 127.0.0.1:1636:636 # LDAP (TLS)
restart: unless-stopped

View File

@@ -1,44 +0,0 @@
{
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:latest": {
"version": "1.0.5",
"resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a",
"integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a"
},
"ghcr.io/devcontainers-extra/features/apt-get-packages": {
"version": "1.0.8",
"resolved": "ghcr.io/devcontainers-extra/features/apt-get-packages@sha256:776fa11c30c50fa91d5e1bf050444b0f130827457109c349db92e8e126b750ec",
"integrity": "sha256:776fa11c30c50fa91d5e1bf050444b0f130827457109c349db92e8e126b750ec"
},
"ghcr.io/devcontainers-extra/features/shellcheck:1": {
"version": "1.1.0",
"resolved": "ghcr.io/devcontainers-extra/features/shellcheck@sha256:8e6d050e4ead6a0350dbe4c70611e351f32cb61a8cc5cb2b39da0cf29ff3bed5",
"integrity": "sha256:8e6d050e4ead6a0350dbe4c70611e351f32cb61a8cc5cb2b39da0cf29ff3bed5"
},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"version": "1.10.0",
"resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e",
"integrity": "sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e"
},
"ghcr.io/devcontainers/features/go:1": {
"version": "1.3.4",
"resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032",
"integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032"
},
"ghcr.io/devcontainers/features/node:2": {
"version": "2.1.0",
"resolved": "ghcr.io/devcontainers/features/node@sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857",
"integrity": "sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857"
},
"ghcr.io/rails/devcontainer/features/postgres-client": {
"version": "1.2.0",
"resolved": "ghcr.io/rails/devcontainer/features/postgres-client@sha256:7e6b118646d6e0d82a9ec06e81ad1b5406f7042f0279d35fff3a7a612be7a050",
"integrity": "sha256:7e6b118646d6e0d82a9ec06e81ad1b5406f7042f0279d35fff3a7a612be7a050"
},
"ghcr.io/rapidsai/devcontainers/features/gitlab-cli:latest": {
"version": "26.8.2",
"resolved": "ghcr.io/rapidsai/devcontainers/features/gitlab-cli@sha256:45b15ff1035cb7c2ac657ee069f9c548744dd6d70311f7abbd3aea12c5ec0c92",
"integrity": "sha256:45b15ff1035cb7c2ac657ee069f9c548744dd6d70311f7abbd3aea12c5ec0c92"
}
}
}

View File

@@ -1,50 +0,0 @@
// For format details, see https://containers.dev/implementors/json_reference/.
// For config options, see the README at: https://github.com/devcontainers/templates/tree/main/src/ruby
{
"name": "Zammad (with LDAP)",
"dockerComposeFile": "compose.yaml",
"service": "devcontainer",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"moby": false
},
"ghcr.io/devcontainers-extra/features/shellcheck:1": {},
"ghcr.io/devcontainers/features/node:2": {
"installYarnUsingApt": "false"
},
"ghcr.io/rails/devcontainer/features/postgres-client": {},
"ghcr.io/devcontainers-extra/features/apt-get-packages": {
"packages": "curl gnupg libimlib2-dev"
},
"ghcr.io/devcontainers/features/go:1": {},
"ghcr.io/rapidsai/devcontainers/features/gitlab-cli:latest": {},
"ghcr.io/anthropics/devcontainer-features/claude-code:latest": {},
"../features/zammad-deps": {},
"../features/add-bin-path": {
"addBinPath": "/workspaces/${localWorkspaceFolderBasename}/bin"
}
},
"mounts": [
{
// Avoid conflicts with host-installed node_modules with a different platform/architecture.
"source": "node-modules",
"target": "/workspaces/${localWorkspaceFolderBasename}/node_modules",
"type": "volume"
},
{
"source": "pnpm-store",
"target": "/workspaces/${localWorkspaceFolderBasename}/.pnpm-store",
"type": "volume"
}
],
"containerEnv": {},
"forwardPorts": [
3000,
3001,
3036,
6042
],
"postCreateCommand": "/workspaces/${localWorkspaceFolderBasename}/.devcontainer/with-ldap/scripts/post_create.sh",
"postStartCommand": "git config --global --add safe.directory /workspaces/${localWorkspaceFolderBasename}"
}

View File

@@ -1,23 +0,0 @@
FROM alpine:3
RUN apk add --no-cache \
openldap \
openldap-back-mdb \
openldap-clients \
openssl
ENV LDAP_BASE_DN="dc=foo,dc=example,dc=com"
# LDAP_ADMIN_PASSWORD defaults to "test" in the entrypoint — override at runtime
ENV LDAP_PORT="389"
ENV LDAP_LDAPS_PORT="636"
COPY docker-entrypoint.sh /usr/local/bin/
COPY bootstrap/ldif/ /ldifs/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh \
&& mkdir -p /var/lib/openldap/openldap-data \
&& chown -R ldap:ldap /ldifs /var/lib/openldap
EXPOSE 389 636
ENTRYPOINT ["docker-entrypoint.sh"]

View File

@@ -1,302 +0,0 @@
dn: dc=foo,dc=example,dc=com
objectClass: top
objectClass: domain
dc: foo
dn: cn=nb,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Nicole
sn: Braun
uid: nb
uidNumber: 5000
gidNumber: 5000
userPassword: testnb
homeDirectory: /home/nb
loginShell: /bin/sh
gecos: Comments
mail: nb@example.com
telephoneNumber: 110
roomNumber: 0200
dn: cn=ab,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Albert
sn: Braun
uid: ab
uidNumber: 5001
gidNumber: 5000
userPassword: testab
homeDirectory: /home/ab
loginShell: /bin/sh
gecos: Comments
mail: ab@example.com
telephoneNumber: 111
roomNumber: 0201
dn: cn=bb,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Berta
sn: Braun
uid: bb
uidNumber: 5002
gidNumber: 5000
userPassword: testbb
homeDirectory: /home/bb
loginShell: /bin/sh
gecos: Comments
mail: bb@example.com
telephoneNumber: 112
roomNumber: 0202
dn: cn=fb,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Frida
sn: Braun
uid: fb
uidNumber: 5003
gidNumber: 5000
userPassword: testfb
homeDirectory: /home/fb
loginShell: /bin/sh
gecos: Comments
mail: fb@example.com
telephoneNumber: 113
roomNumber: 0203
dn: cn=lb,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Lena
sn: Braun
uid: lb
uidNumber: 5004
gidNumber: 5000
userPassword: testlb
homeDirectory: /home/lb
loginShell: /bin/sh
gecos: Comments
mail: lb@example.com
telephoneNumber: 114
roomNumber: 0204
dn: cn=eb,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Elke
sn: Braun
uid: eb
uidNumber: 5005
gidNumber: 5000
userPassword: testeb
homeDirectory: /home/eb
loginShell: /bin/sh
gecos: Comments
mail: eb@example.com
telephoneNumber: 115
roomNumber: 0205
dn: cn=customer1,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Customer
sn: 1
uid: customer1
uidNumber: 6001
gidNumber: 6000
userPassword: testc1
homeDirectory: /home/customer1
loginShell: /bin/sh
gecos: Comments
mail: customer1@example.com
telephoneNumber: 1001
roomNumber: 2001
dn: cn=customer2,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Customer
sn: 2
uid: customer2
uidNumber: 6002
gidNumber: 6000
userPassword: testc2
homeDirectory: /home/customer2
loginShell: /bin/sh
gecos: Comments
mail: customer2@example.com
telephoneNumber: 1002
roomNumber: 2002
dn: cn=customer3,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Customer
sn: 3
uid: customer3
uidNumber: 6003
gidNumber: 6000
userPassword: testc3
homeDirectory: /home/customer3
loginShell: /bin/sh
gecos: Comments
mail: customer3@example.com
telephoneNumber: 1003
roomNumber: 2003
dn: cn=customer4,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Customer
sn: 4
uid: customer4
uidNumber: 6004
gidNumber: 6000
userPassword: testc4
homeDirectory: /home/customer4
loginShell: /bin/sh
gecos: Comments
mail: customer4@example.com
telephoneNumber: 1004
roomNumber: 2004
dn: cn=customer5,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Customer
sn: 5
uid: customer5
uidNumber: 6005
gidNumber: 6000
userPassword: testc5
homeDirectory: /home/customer5
loginShell: /bin/sh
gecos: Comments
mail: customer5@example.com
telephoneNumber: 1005
roomNumber: 2005
dn: cn=customer6,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Customer
sn: 6
uid: customer6
uidNumber: 6006
gidNumber: 6000
userPassword: testc6
homeDirectory: /home/customer6
loginShell: /bin/sh
gecos: Comments
mail: customer6@example.com
telephoneNumber: 1000
roomNumber: 2000
dn: cn=customer7,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Customer
sn: 7
uid: customer7
uidNumber: 6007
gidNumber: 6000
userPassword: testc7
homeDirectory: /home/customer7
loginShell: /bin/sh
gecos: Comments
mail: customer7@example.com
telephoneNumber: 1007
roomNumber: 2007
dn: cn=customer8,dc=foo,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
cn: Customer
sn: 8
uid: customer8
uidNumber: 6008
gidNumber: 6000
userPassword: testc8
homeDirectory: /home/customer8
loginShell: /bin/sh
gecos: Comments
mail: customer8@example.com
telephoneNumber: 1008
roomNumber: 2008
dn: ou=groups,dc=foo,dc=example,dc=com
objectClass: organizationalUnit
ou: groups
dn: cn=admin,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=ab,dc=foo,dc=example,dc=com
member: cn=bb,dc=foo,dc=example,dc=com
dn: cn=1st level,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=eb,dc=foo,dc=example,dc=com
member: cn=lb,dc=foo,dc=example,dc=com
dn: cn=2nd level,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=fb,dc=foo,dc=example,dc=com
member: cn=lb,dc=foo,dc=example,dc=com
dn: cn=3rd level,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=3rd level - north,ou=groups,dc=foo,dc=example,dc=com
member: cn=3rd level - west,ou=groups,dc=foo,dc=example,dc=com
dn: cn=3rd level - north,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=fb,dc=foo,dc=example,dc=com
dn: cn=3rd level - west,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=lb,dc=foo,dc=example,dc=com
dn: cn=4th level,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=4th level - north,ou=groups,dc=foo,dc=example,dc=com
member: cn=4th level - west,ou=groups,dc=foo,dc=example,dc=com
dn: cn=4th level - north,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=ab,dc=foo,dc=example,dc=com
dn: cn=4th level - west,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=bb,dc=foo,dc=example,dc=com
dn: cn=sales,ou=groups,dc=foo,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
member: cn=ab,dc=foo,dc=example,dc=com
member: cn=nb,dc=foo,dc=example,dc=com

View File

@@ -1,125 +0,0 @@
#!/bin/sh
set -eu
# Configurable settings with defaults
LDAP_BASE_DN="${LDAP_BASE_DN:-dc=foo,dc=example,dc=com}"
LDAP_ADMIN_DN="${LDAP_ADMIN_DN:-cn=admin,${LDAP_BASE_DN}}"
LDAP_ADMIN_PASSWORD="${LDAP_ADMIN_PASSWORD:-test}"
LDAP_DISALLOW_BIND_ANON="${LDAP_DISALLOW_BIND_ANON:-false}"
# Unconfigurable settings
LDAP_PORT=389
LDAP_LDAPS_PORT=636
LDAP_TLS_CERT_FILE="/certs/ldap.crt"
LDAP_TLS_KEY_FILE="/certs/ldap.key"
LDAP_TLS_CA_FILE="/certs/ca.crt"
DATA_DIR="/var/lib/openldap/openldap-data"
SLAPD_CONF="/etc/openldap/slapd.conf"
INIT_FLAG="${DATA_DIR}/.initialized"
# Generate TLS certificates if they don't already exist (e.g. first start or fresh volume).
if [ ! -f "${LDAP_TLS_KEY_FILE}" ]; then
echo "==> Generating self-signed TLS certificates..."
CERTS_DIR="$(dirname "${LDAP_TLS_CERT_FILE}")"
mkdir -p "${CERTS_DIR}"
# CA key + self-signed CA certificate
openssl genrsa -out /tmp/ca.key 4096 2>/dev/null
openssl req -new -x509 -days 3650 \
-key /tmp/ca.key \
-out "${LDAP_TLS_CA_FILE}" \
-subj "/CN=Zammad Dev LDAP CA/O=Zammad Dev" 2>/dev/null
# Server key + CSR
openssl genrsa -out "${LDAP_TLS_KEY_FILE}" 2048 2>/dev/null
openssl req -new \
-key "${LDAP_TLS_KEY_FILE}" \
-out /tmp/ldap.csr \
-subj "/CN=ldap/O=Zammad Dev" 2>/dev/null
# SAN extension so Ruby's OpenSSL accepts the certificate
printf 'subjectAltName=DNS:ldap,DNS:localhost,IP:127.0.0.1\n' > /tmp/ldap.ext
# Sign server certificate with CA
openssl x509 -req -days 3650 \
-in /tmp/ldap.csr \
-CA "${LDAP_TLS_CA_FILE}" \
-CAkey /tmp/ca.key \
-set_serial 1 \
-out "${LDAP_TLS_CERT_FILE}" \
-extfile /tmp/ldap.ext 2>/dev/null
chown ldap:ldap "${LDAP_TLS_KEY_FILE}" "${LDAP_TLS_CERT_FILE}" "${LDAP_TLS_CA_FILE}"
rm -f /tmp/ca.key /tmp/ldap.csr /tmp/ldap.ext
echo "==> TLS certificates generated."
fi
# Regenerate slapd.conf on every startup so env-var changes are picked up.
HASHED_PASS=$(slappasswd -s "${LDAP_ADMIN_PASSWORD}")
DISALLOW_DIRECTIVE=""
[ "${LDAP_DISALLOW_BIND_ANON}" = "true" ] && DISALLOW_DIRECTIVE="disallow bind_anon"
cat > "${SLAPD_CONF}" <<EOF
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/inetorgperson.schema
include /etc/openldap/schema/nis.schema
modulepath /usr/lib/openldap
moduleload back_mdb
pidfile /tmp/slapd.pid
argsfile /tmp/slapd.args
TLSCertificateFile ${LDAP_TLS_CERT_FILE}
TLSCertificateKeyFile ${LDAP_TLS_KEY_FILE}
TLSCACertificateFile ${LDAP_TLS_CA_FILE}
${DISALLOW_DIRECTIVE}
database mdb
suffix "${LDAP_BASE_DN}"
rootdn "${LDAP_ADMIN_DN}"
rootpw ${HASHED_PASS}
directory ${DATA_DIR}
maxsize 1073741824
access to attrs=userPassword
by dn.base="${LDAP_ADMIN_DN}" manage
by anonymous auth
by * none
access to *
by dn.base="${LDAP_ADMIN_DN}" manage
by users read
by * none
EOF
if [ ! -f "${INIT_FLAG}" ]; then
echo "==> First-time initialization..."
for ldif in /ldifs/*.ldif; do
basename=$(basename "${ldif}")
# Skip cn=config LDIF files — their settings are handled via slapd.conf above.
if ! grep -q "^dn: cn=config" "${ldif}" 2>/dev/null; then
echo " -> data: ${basename}"
slapadd -f "${SLAPD_CONF}" -l "${ldif}"
fi
done
chown -R ldap:ldap "${DATA_DIR}"
touch "${INIT_FLAG}"
echo "==> Initialization complete."
fi
echo "==> Starting slapd on ldap://0.0.0.0:${LDAP_PORT} and ldaps://0.0.0.0:${LDAP_LDAPS_PORT}..."
exec /usr/sbin/slapd \
-h "ldap://0.0.0.0:${LDAP_PORT}/ ldaps://0.0.0.0:${LDAP_LDAPS_PORT}/" \
-u ldap \
-g ldap \
-f "${SLAPD_CONF}" \
-d 256

View File

@@ -1,79 +0,0 @@
#!/usr/bin/env sh
env
# Ensure proper permissions on root-mounted volumes.
sudo chown -R "${USER}" node_modules .pnpm-store
export SHELL=/bin/bash
pnpm setup
export PNPM_HOME="${HOME}/.local/share/pnpm"
export PATH="${PNPM_HOME}/bin:${PATH}"
pnpm config set store-dir "$(pwd)/.pnpm-store" --global
bin/setup --skip-server
echo "== Initialize CodeGraph index =="
pnpm run codegraph:init
echo "== Precompile Ruby cache =="
bundle exec bootsnap precompile --gemfile app/ lib/
echo "== Configure Elasticsearch URL =="
bundle exec rails r "Setting.set('es_url', 'http://elasticsearch:9200')"
echo "== Run auto_wizard setup =="
bundle exec rails zammad:setup:auto_wizard
echo "== Import LDAP CA certificate =="
bundle exec rails r "SSLCertificate.create!(certificate: Rails.root.join('.devcontainer/with-ldap/ldap/certs/ca.crt').read)"
echo "== Configure LDAP source =="
bundle exec rails r "
Setting.set('ldap_integration', true)
LdapSource.find_by(name: 'Zammad LDAP')&.destroy
LdapSource.create!(
name: 'Zammad LDAP',
active: true,
preferences: {
'host' => 'ldap',
'ssl' => 'ssl',
'ssl_verify' => true,
'options' => { 'dc=foo,dc=example,dc=com' => 'dc=foo,dc=example,dc=com' },
'option' => 'dc=foo,dc=example,dc=com',
'base_dn' => 'dc=foo,dc=example,dc=com',
'bind_user' => 'cn=admin,dc=foo,dc=example,dc=com',
'bind_pw' => 'test',
'user_uid' => 'uid',
'user_filter' => '(objectClass=posixaccount)',
'group_uid' => 'dn',
'group_filter' => '(objectClass=groupOfNames)',
'user_attributes' => { 'cn' => 'firstname', 'sn' => 'lastname', 'mail' => 'email', 'uid' => 'login', 'telephonenumber' => 'phone' },
'group_role_map' => {
'cn=admin,ou=groups,dc=foo,dc=example,dc=com' => ['1'],
'cn=1st level,ou=groups,dc=foo,dc=example,dc=com' => ['2'],
},
'unassigned_users' => 'sigup_roles',
},
created_by_id: 1,
updated_by_id: 1,
)
"
echo "== Rebuild search index =="
bundle exec rails zammad:searchindex:rebuild
echo "== Precompile frontend assets =="
bundle exec rails assets:precompile
echo "== Prepare test database =="
RAILS_ENV='test' bundle exec rails db:drop db:create zammad:ci:test:prepare || exit 1

View File

@@ -1,83 +0,0 @@
{
"Users": [
{
"login": "admin@example.com",
"firstname": "Test Admin",
"lastname": "Agent",
"email": "admin@example.com",
"password": "test"
},
{
"login": "agent1@example.com",
"firstname": "Agent 1",
"lastname": "Test",
"email": "agent1@example.com",
"password": "test",
"roles": ["Agent"]
}
],
"Groups": [
{
"name": "some group1",
"users": ["admin@example.com", "agent1@example.com"]
},
{
"name": "Users",
"users": ["admin@example.com", "agent1@example.com"],
"signature": "default",
"email_address_id": 1
}
],
"Channels": [
{
"id": 3,
"area": "Email::Account",
"group": "Users",
"active": true,
"options": {
"inbound": {
"adapter": "imap",
"options": {
"host": "mail.test.local",
"port": "993",
"ssl": "ssl",
"user": "zammad@test.local",
"password": "zammad",
"ssl_verify": true
}
},
"outbound": {
"adapter": "smtp",
"options": {
"host": "mail.test.local",
"port": "465",
"user": "zammad@test.local",
"password": "zammad",
"ssl_verify": true
}
}
}
}
],
"EmailAddresses": [
{
"id": 1,
"channel_id": 3,
"name": "Zammad Helpdesk",
"email": "zammad@test.local"
}
],
"Settings": [
{
"name": "product_name",
"value": "Zammad Test System"
},
{
"name": "developer_mode",
"value": true
}
],
"TextModuleLocale": {
"Locale": "de-de"
}
}

View File

@@ -1,46 +0,0 @@
name: "zammad-devcontainer"
include: [../default/compose.yaml]
services:
mailserver-init:
image: ghcr.io/docker-mailserver/docker-mailserver:latest
entrypoint: ["/bin/sh", "-c", "sh /tmp/docker-mailserver/scripts/init-mailserver.sh"]
environment:
ACCOUNT_PROVISIONER: FILE
volumes:
- ./scripts:/tmp/docker-mailserver/scripts:ro
- ./certs:/tmp/docker-mailserver/certs
- mailserver-config:/tmp/docker-mailserver
restart: no
mailserver:
image: ghcr.io/docker-mailserver/docker-mailserver:latest
hostname: mail.test.local
domainname: test.local
environment:
SSL_TYPE: manual
SSL_CERT_PATH: /tmp/docker-mailserver/certs/mail.test.local.crt
SSL_KEY_PATH: /tmp/docker-mailserver/certs/mail.test.local.key
ENABLE_IMAP: "1"
ENABLE_POP3: "1"
DMS_DEBUG: "0"
ONE_DIR: "1"
POSTMASTER_ADDRESS: postmaster@test.local
OVERRIDE_HOSTNAME: mail.test.local
ACCOUNT_PROVISIONER: FILE
depends_on:
mailserver-init:
condition: service_completed_successfully
ports:
- 127.0.0.1:1465:465 # SMTP (SSL)
- 127.0.0.1:1587:587 # SMTP (STARTTLS)
- 127.0.0.1:1993:993 # IMAP (SSL)
volumes:
- ./certs:/tmp/docker-mailserver/certs
- mailserver-config:/tmp/docker-mailserver
restart: unless-stopped
volumes:
mailserver-config:
driver: local

View File

@@ -1,44 +0,0 @@
{
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:latest": {
"version": "1.0.5",
"resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a",
"integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a"
},
"ghcr.io/devcontainers-extra/features/apt-get-packages": {
"version": "1.0.8",
"resolved": "ghcr.io/devcontainers-extra/features/apt-get-packages@sha256:776fa11c30c50fa91d5e1bf050444b0f130827457109c349db92e8e126b750ec",
"integrity": "sha256:776fa11c30c50fa91d5e1bf050444b0f130827457109c349db92e8e126b750ec"
},
"ghcr.io/devcontainers-extra/features/shellcheck:1": {
"version": "1.1.0",
"resolved": "ghcr.io/devcontainers-extra/features/shellcheck@sha256:8e6d050e4ead6a0350dbe4c70611e351f32cb61a8cc5cb2b39da0cf29ff3bed5",
"integrity": "sha256:8e6d050e4ead6a0350dbe4c70611e351f32cb61a8cc5cb2b39da0cf29ff3bed5"
},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"version": "1.10.0",
"resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e",
"integrity": "sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e"
},
"ghcr.io/devcontainers/features/go:1": {
"version": "1.3.4",
"resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032",
"integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032"
},
"ghcr.io/devcontainers/features/node:2": {
"version": "2.1.0",
"resolved": "ghcr.io/devcontainers/features/node@sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857",
"integrity": "sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857"
},
"ghcr.io/rails/devcontainer/features/postgres-client": {
"version": "1.2.0",
"resolved": "ghcr.io/rails/devcontainer/features/postgres-client@sha256:7e6b118646d6e0d82a9ec06e81ad1b5406f7042f0279d35fff3a7a612be7a050",
"integrity": "sha256:7e6b118646d6e0d82a9ec06e81ad1b5406f7042f0279d35fff3a7a612be7a050"
},
"ghcr.io/rapidsai/devcontainers/features/gitlab-cli:latest": {
"version": "26.8.2",
"resolved": "ghcr.io/rapidsai/devcontainers/features/gitlab-cli@sha256:45b15ff1035cb7c2ac657ee069f9c548744dd6d70311f7abbd3aea12c5ec0c92",
"integrity": "sha256:45b15ff1035cb7c2ac657ee069f9c548744dd6d70311f7abbd3aea12c5ec0c92"
}
}
}

View File

@@ -1,55 +0,0 @@
// For format details, see https://containers.dev/implementors/json_reference/.
// For config options, see the README at: https://github.com/devcontainers/templates/tree/main/src/ruby
{
"name": "Zammad (with Mailserver)",
"dockerComposeFile": "compose.yaml",
"service": "devcontainer",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"moby": false
},
"ghcr.io/devcontainers-extra/features/shellcheck:1": {},
"ghcr.io/devcontainers/features/node:2": {
"installYarnUsingApt": "false"
},
"ghcr.io/rails/devcontainer/features/postgres-client": {},
"ghcr.io/devcontainers-extra/features/apt-get-packages": {
"packages": "curl gnupg libimlib2-dev"
},
"ghcr.io/devcontainers/features/go:1": {},
"ghcr.io/rapidsai/devcontainers/features/gitlab-cli:latest": {},
"ghcr.io/anthropics/devcontainer-features/claude-code:latest": {},
"../features/zammad-deps": {},
"../features/add-bin-path": {
"addBinPath": "/workspaces/${localWorkspaceFolderBasename}/bin"
}
},
"mounts": [
{
// Avoid conflicts with host-installed node_modules with a different platform/architecture.
"source": "node-modules",
"target": "/workspaces/${localWorkspaceFolderBasename}/node_modules",
"type": "volume"
},
{
"source": "pnpm-store",
"target": "/workspaces/${localWorkspaceFolderBasename}/.pnpm-store",
"type": "volume"
}
],
"containerEnv": {
// ZAMMAD_MAIL_TO_FILE is for debugging outgoing mails.
// It allows to easily inspect contents of the outgoing messages that use `sendmail` adapter,
// by writing them to files instead of sending them. Check the `tmp/mails` directory for the generated files.
"ZAMMAD_MAIL_TO_FILE": "1"
},
"forwardPorts": [
3000,
3001,
3036,
6042
],
"postCreateCommand": "/workspaces/${localWorkspaceFolderBasename}/.devcontainer/with-mailserver/scripts/post_create.sh",
"postStartCommand": "git config --global --add safe.directory /workspaces/${localWorkspaceFolderBasename}"
}

View File

@@ -1,42 +0,0 @@
#!/bin/sh
set -eu
CERT_DIR=/tmp/docker-mailserver/certs
CONFIG_DIR=/tmp/docker-mailserver
CERT_FILE="$CERT_DIR/mail.test.local.crt"
KEY_FILE="$CERT_DIR/mail.test.local.key"
POSTFIX_ACCOUNTS_FILE="$CONFIG_DIR/postfix-accounts.cf"
POSTFIX_VIRTUAL_FILE="$CONFIG_DIR/postfix-virtual.cf"
POSTFIX_MAIN_FILE="$CONFIG_DIR/postfix-main.cf"
mkdir -p "$CERT_DIR"
mkdir -p "$CONFIG_DIR"
if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then
openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes \
-keyout "$KEY_FILE" \
-out "$CERT_FILE" \
-subj "/CN=mail.test.local" \
-addext "subjectAltName=DNS:mail.test.local,DNS:localhost"
fi
if [ ! -f "$POSTFIX_ACCOUNTS_FILE" ]; then
touch "$POSTFIX_ACCOUNTS_FILE"
setup email add zammad@test.local zammad
elif ! setup email list | grep -q 'zammad@test.local$'; then
setup email add zammad@test.local zammad
fi
if [ ! -f "$POSTFIX_VIRTUAL_FILE" ]; then
touch "$POSTFIX_VIRTUAL_FILE"
setup alias add zammad@localhost zammad@test.local
elif ! setup alias list | grep -q 'zammad@localhost'; then
setup alias add zammad@localhost zammad@test.local
fi
cat > "$POSTFIX_MAIN_FILE" <<'EOF'
smtpd_recipient_restrictions = permit_sasl_authenticated,reject
relay_transport = discard:
default_transport = discard:
EOF

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env sh
env
# Ensure proper permissions on root-mounted volumes.
sudo chown -R "${USER}" node_modules .pnpm-store
export SHELL=/bin/bash
pnpm setup
export PNPM_HOME="${HOME}/.local/share/pnpm"
export PATH="${PNPM_HOME}/bin:${PATH}"
pnpm config set store-dir "$(pwd)/.pnpm-store" --global
bin/setup --skip-server
echo "== Initialize CodeGraph index =="
pnpm run codegraph:init
echo "== Precompile Ruby cache =="
bundle exec bootsnap precompile --gemfile app/ lib/
echo "== Configure Elasticsearch URL =="
bundle exec rails r "Setting.set('es_url', 'http://elasticsearch:9200')"
echo "== Run auto_wizard setup =="
bundle exec rails zammad:setup:auto_wizard[.devcontainer/with-mailserver/auto_wizard_mailserver.json]
CERT_PATH=.devcontainer/with-mailserver/certs/mail.test.local.crt
# Import self-signed certificate for mailserver.
if [ -f "$CERT_PATH" ]; then
echo "== Import self-signed certificate for mailserver =="
bundle exec rails r "SSLCertificate.create!(certificate: Rails.root.join('$CERT_PATH').read)"
fi
echo "== Rebuild search index =="
bundle exec rails zammad:searchindex:rebuild
echo "== Precompile frontend assets =="
bundle exec rails assets:precompile
echo "== Prepare test database =="
RAILS_ENV='test' bundle exec rails db:drop db:create zammad:ci:test:prepare || exit 1

Some files were not shown because too many files have changed in this diff Show More