From f994dfadac3c91c65914eb4a930487a1e8c3ffea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 18:13:38 -0400 Subject: [PATCH 01/42] chore(deps): update electron-builder to v26 (#1944) * chore(deps): update electron-builder to v26 * remove extendinfo keys which cause signing to crash Signed-off-by: Adam Setch --------- Signed-off-by: Adam Setch Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Adam Setch --- config/electron-builder.js | 6 - package.json | 2 +- pnpm-lock.yaml | 298 ++++++++++++++----------------------- 3 files changed, 109 insertions(+), 197 deletions(-) diff --git a/config/electron-builder.js b/config/electron-builder.js index 3bd2d15fe..c4675dd0e 100644 --- a/config/electron-builder.js +++ b/config/electron-builder.js @@ -37,12 +37,6 @@ const config = { entitlements: 'assets/entitlements.mac.plist', entitlementsInherit: 'assets/entitlements.mac.plist', gatekeeperAssess: false, - extendInfo: { - NSBluetoothAlwaysUsageDescription: null, - NSBluetoothPeripheralUsageDescription: null, - NSCameraUsageDescription: null, - NSMicrophoneUsageDescription: null, - }, }, dmg: { icon: 'assets/images/app-icon.icns', diff --git a/package.json b/package.json index bbc283abb..a34543a81 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "css-minimizer-webpack-plugin": "7.0.2", "date-fns": "4.1.0", "electron": "37.3.1", - "electron-builder": "25.1.8", + "electron-builder": "26.0.12", "final-form": "5.0.0", "graphql-tag": "2.12.6", "html-webpack-plugin": "5.6.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3036e9a97..8afb7e3a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,8 +106,8 @@ importers: specifier: 37.3.1 version: 37.3.1 electron-builder: - specifier: 25.1.8 - version: 25.1.8(electron-builder-squirrel-windows@24.13.3) + specifier: 26.0.12 + version: 26.0.12(electron-builder-squirrel-windows@24.13.3) final-form: specifier: 5.0.0 version: 5.0.0 @@ -471,10 +471,20 @@ packages: engines: {node: '>=10.12.0'} hasBin: true + '@electron/fuses@1.8.0': + resolution: {integrity: sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==} + hasBin: true + '@electron/get@2.0.3': resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} engines: {node: '>=12'} + '@electron/node-gyp@https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2': + resolution: {tarball: https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2} + version: 10.2.0-electron.1 + engines: {node: '>=12.13.0'} + hasBin: true + '@electron/notarize@2.2.1': resolution: {integrity: sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==} engines: {node: '>= 10.0.0'} @@ -497,8 +507,8 @@ packages: engines: {node: '>=12.0.0'} hasBin: true - '@electron/rebuild@3.6.1': - resolution: {integrity: sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==} + '@electron/rebuild@3.7.0': + resolution: {integrity: sha512-VW++CNSlZwMYP7MyXEbrKjpzEwhB5kDNbzGtiPEjwYysqyTCF+YbNJ210Dj3AjWsGSV4iEEwNkmJN9yGZmVvmw==} engines: {node: '>=12.13.0'} hasBin: true @@ -1418,8 +1428,8 @@ packages: app-builder-bin@4.0.0: resolution: {integrity: sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==} - app-builder-bin@5.0.0-alpha.10: - resolution: {integrity: sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==} + app-builder-bin@5.0.0-alpha.12: + resolution: {integrity: sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==} app-builder-lib@24.13.3: resolution: {integrity: sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==} @@ -1428,15 +1438,12 @@ packages: dmg-builder: 24.13.3 electron-builder-squirrel-windows: 24.13.3 - app-builder-lib@25.1.8: - resolution: {integrity: sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==} + app-builder-lib@26.0.12: + resolution: {integrity: sha512-+/CEPH1fVKf6HowBUs6LcAIoRcjeqgvAeoSE+cl7Y7LndyQ9ViGPYibNk7wmhMHzNgHIuIbw4nWADPO+4mjgWw==} engines: {node: '>=14.0.0'} peerDependencies: - dmg-builder: 25.1.8 - electron-builder-squirrel-windows: 25.1.8 - - aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dmg-builder: 26.0.12 + electron-builder-squirrel-windows: 26.0.12 archiver-utils@2.1.0: resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} @@ -1450,11 +1457,6 @@ packages: resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} engines: {node: '>= 10'} - are-we-there-yet@3.0.1: - resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -1577,10 +1579,6 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - builder-util-runtime@9.2.10: - resolution: {integrity: sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==} - engines: {node: '>=12.0.0'} - builder-util-runtime@9.2.4: resolution: {integrity: sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==} engines: {node: '>=12.0.0'} @@ -1592,8 +1590,8 @@ packages: builder-util@24.13.1: resolution: {integrity: sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==} - builder-util@25.1.7: - resolution: {integrity: sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==} + builder-util@26.0.11: + resolution: {integrity: sha512-xNjXfsldUEe153h1DraD0XvDOpqGR0L5eKFkdReB7eFW5HqysDZFfly4rckda6y9dF39N3pkPlOblcfHKGw+uA==} cacache@16.1.3: resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} @@ -1739,10 +1737,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - color2k@2.0.3: resolution: {integrity: sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==} @@ -1800,9 +1794,6 @@ packages: config-file-ts@0.2.8-rc1: resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==} - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2021,9 +2012,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2053,8 +2041,8 @@ packages: dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} - dmg-builder@25.1.8: - resolution: {integrity: sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==} + dmg-builder@26.0.12: + resolution: {integrity: sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==} dmg-license@1.0.11: resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==} @@ -2127,8 +2115,8 @@ packages: electron-builder-squirrel-windows@24.13.3: resolution: {integrity: sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==} - electron-builder@25.1.8: - resolution: {integrity: sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==} + electron-builder@26.0.12: + resolution: {integrity: sha512-cD1kz5g2sgPTMFHjLxfMjUK5JABq3//J4jPswi93tOPFz6btzXYtK5NrDt717NRbukCUDOrrvmYVOWERlqoiXA==} engines: {node: '>=14.0.0'} hasBin: true @@ -2142,8 +2130,8 @@ packages: electron-publish@24.13.1: resolution: {integrity: sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==} - electron-publish@25.1.7: - resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==} + electron-publish@26.0.11: + resolution: {integrity: sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A==} electron-to-chromium@1.5.50: resolution: {integrity: sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==} @@ -2362,10 +2350,6 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} @@ -2407,11 +2391,6 @@ packages: fzy.js@0.4.1: resolution: {integrity: sha512-4sPVXf+9oGhzg2tYzgWe4hgAY0wEbkqeuKVEgdnqX8S8VcLosQsDjb0jV+f5uoQlf8INWId1w0IGoufAoik1TA==} - gauge@4.0.4: - resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -2529,9 +2508,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -2596,10 +2572,6 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - https-proxy-agent@7.0.5: - resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} - engines: {node: '>= 14'} - https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -3181,10 +3153,6 @@ packages: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} - lru-cache@10.2.0: - resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} - engines: {node: 14 || >=16.14} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3475,11 +3443,6 @@ packages: node-api-version@0.2.0: resolution: {integrity: sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg==} - node-gyp@9.4.1: - resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} - engines: {node: ^12.13 || ^14.13 || >=16} - hasBin: true - node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -3503,11 +3466,6 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - npmlog@6.0.2: - resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -3863,6 +3821,10 @@ packages: resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + proc-log@2.0.1: + resolution: {integrity: sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -4106,6 +4068,10 @@ packages: semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4122,9 +4088,6 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} @@ -4362,6 +4325,9 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + tiny-async-pool@1.3.0: + resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==} + tiny-typed-emitter@2.1.0: resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} @@ -4641,9 +4607,6 @@ packages: engines: {node: '>= 8'} hasBin: true - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} @@ -5028,6 +4991,12 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + '@electron/fuses@1.8.0': + dependencies: + chalk: 4.1.2 + fs-extra: 9.1.0 + minimist: 1.2.8 + '@electron/get@2.0.3': dependencies: debug: 4.4.0 @@ -5042,6 +5011,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@electron/node-gyp@https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2': + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.1 + glob: 8.1.0 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1 + nopt: 6.0.0 + proc-log: 2.0.1 + semver: 7.7.2 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + '@electron/notarize@2.2.1': dependencies: debug: 4.4.0 @@ -5087,8 +5072,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron/rebuild@3.6.1': + '@electron/rebuild@3.7.0': dependencies: + '@electron/node-gyp': https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 debug: 4.4.0 @@ -5097,7 +5083,6 @@ snapshots: got: 11.8.6 node-abi: 3.67.0 node-api-version: 0.2.0 - node-gyp: 9.4.1 ora: 5.4.1 read-binary-file-arch: 1.0.6 semver: 7.7.2 @@ -6169,9 +6154,9 @@ snapshots: app-builder-bin@4.0.0: {} - app-builder-bin@5.0.0-alpha.10: {} + app-builder-bin@5.0.0-alpha.12: {} - app-builder-lib@24.13.3(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3): + app-builder-lib@24.13.3(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.2.1 @@ -6185,9 +6170,9 @@ snapshots: builder-util-runtime: 9.2.4 chromium-pickle-js: 0.2.0 debug: 4.4.0 - dmg-builder: 25.1.8(electron-builder-squirrel-windows@24.13.3) + dmg-builder: 26.0.12(electron-builder-squirrel-windows@24.13.3) ejs: 3.1.10 - electron-builder-squirrel-windows: 24.13.3(dmg-builder@25.1.8) + electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.0.12) electron-publish: 24.13.1 form-data: 4.0.4 fs-extra: 10.1.0 @@ -6205,29 +6190,29 @@ snapshots: transitivePeerDependencies: - supports-color - app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3): + app-builder-lib@26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3): dependencies: '@develar/schema-utils': 2.6.5 + '@electron/asar': 3.2.18 + '@electron/fuses': 1.8.0 '@electron/notarize': 2.5.0 '@electron/osx-sign': 1.3.1 - '@electron/rebuild': 3.6.1 + '@electron/rebuild': 3.7.0 '@electron/universal': 2.0.1 '@malept/flatpak-bundler': 0.4.0 '@types/fs-extra': 9.0.13 async-exit-hook: 2.0.1 - bluebird-lst: 1.0.9 - builder-util: 25.1.7 - builder-util-runtime: 9.2.10 + builder-util: 26.0.11 + builder-util-runtime: 9.3.1 chromium-pickle-js: 0.2.0 config-file-ts: 0.2.8-rc1 debug: 4.4.0 - dmg-builder: 25.1.8(electron-builder-squirrel-windows@24.13.3) + dmg-builder: 26.0.12(electron-builder-squirrel-windows@24.13.3) dotenv: 16.4.5 dotenv-expand: 11.0.6 ejs: 3.1.10 - electron-builder-squirrel-windows: 24.13.3(dmg-builder@25.1.8) - electron-publish: 25.1.7 - form-data: 4.0.0 + electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.0.12) + electron-publish: 26.0.11 fs-extra: 10.1.0 hosted-git-info: 4.1.0 is-ci: 3.0.1 @@ -6236,17 +6221,16 @@ snapshots: json5: 2.2.3 lazy-val: 1.0.5 minimatch: 10.0.1 + plist: 3.1.0 resedit: 1.7.1 - sanitize-filename: 1.6.3 semver: 7.7.2 tar: 6.2.1 temp-file: 3.4.0 + tiny-async-pool: 1.3.0 transitivePeerDependencies: - bluebird - supports-color - aproba@2.0.0: {} - archiver-utils@2.1.0: dependencies: glob: 7.2.3 @@ -6283,11 +6267,6 @@ snapshots: tar-stream: 2.2.0 zip-stream: 4.1.1 - are-we-there-yet@3.0.1: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - arg@4.1.3: {} argparse@1.0.10: @@ -6438,13 +6417,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - builder-util-runtime@9.2.10: - dependencies: - debug: 4.4.0 - sax: 1.3.0 - transitivePeerDependencies: - - supports-color - builder-util-runtime@9.2.4: dependencies: debug: 4.4.0 @@ -6480,24 +6452,25 @@ snapshots: transitivePeerDependencies: - supports-color - builder-util@25.1.7: + builder-util@26.0.11: dependencies: 7zip-bin: 5.2.0 '@types/debug': 4.1.12 - app-builder-bin: 5.0.0-alpha.10 - bluebird-lst: 1.0.9 - builder-util-runtime: 9.2.10 + app-builder-bin: 5.0.0-alpha.12 + builder-util-runtime: 9.3.1 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.0 fs-extra: 10.1.0 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 is-ci: 3.0.1 js-yaml: 4.1.0 + sanitize-filename: 1.6.3 source-map-support: 0.5.21 stat-mode: 1.0.0 temp-file: 3.4.0 + tiny-async-pool: 1.3.0 transitivePeerDependencies: - supports-color @@ -6648,8 +6621,6 @@ snapshots: color-name@1.1.4: {} - color-support@1.1.3: {} - color2k@2.0.3: {} colord@2.9.3: {} @@ -6703,8 +6674,6 @@ snapshots: glob: 10.4.5 typescript: 5.9.2 - console-control-strings@1.1.0: {} - convert-source-map@2.0.0: {} cookie@1.0.2: {} @@ -6925,8 +6894,6 @@ snapshots: delayed-stream@1.0.0: {} - delegates@1.0.0: {} - dequal@2.0.3: {} detect-libc@2.0.4: {} @@ -6950,11 +6917,11 @@ snapshots: minimatch: 3.1.2 p-limit: 3.1.0 - dmg-builder@25.1.8(electron-builder-squirrel-windows@24.13.3): + dmg-builder@26.0.12(electron-builder-squirrel-windows@24.13.3): dependencies: - app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3) - builder-util: 25.1.7 - builder-util-runtime: 9.2.10 + app-builder-lib: 26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3) + builder-util: 26.0.11 + builder-util-runtime: 9.3.1 fs-extra: 10.1.0 iconv-lite: 0.6.3 js-yaml: 4.1.0 @@ -7046,9 +7013,9 @@ snapshots: dependencies: jake: 10.8.7 - electron-builder-squirrel-windows@24.13.3(dmg-builder@25.1.8): + electron-builder-squirrel-windows@24.13.3(dmg-builder@26.0.12): dependencies: - app-builder-lib: 24.13.3(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3) + app-builder-lib: 24.13.3(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3) archiver: 5.3.2 builder-util: 24.13.1 fs-extra: 10.1.0 @@ -7056,13 +7023,13 @@ snapshots: - dmg-builder - supports-color - electron-builder@25.1.8(electron-builder-squirrel-windows@24.13.3): + electron-builder@26.0.12(electron-builder-squirrel-windows@24.13.3): dependencies: - app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3) - builder-util: 25.1.7 - builder-util-runtime: 9.2.10 + app-builder-lib: 26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3) + builder-util: 26.0.11 + builder-util-runtime: 9.3.1 chalk: 4.1.2 - dmg-builder: 25.1.8(electron-builder-squirrel-windows@24.13.3) + dmg-builder: 26.0.12(electron-builder-squirrel-windows@24.13.3) fs-extra: 10.1.0 is-ci: 3.0.1 lazy-val: 1.0.5 @@ -7089,12 +7056,13 @@ snapshots: transitivePeerDependencies: - supports-color - electron-publish@25.1.7: + electron-publish@26.0.11: dependencies: '@types/fs-extra': 9.0.13 - builder-util: 25.1.7 - builder-util-runtime: 9.2.10 + builder-util: 26.0.11 + builder-util-runtime: 9.3.1 chalk: 4.1.2 + form-data: 4.0.4 fs-extra: 10.1.0 lazy-val: 1.0.5 mime: 2.6.0 @@ -7308,12 +7276,6 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.0: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -7362,17 +7324,6 @@ snapshots: fzy.js@0.4.1: {} - gauge@4.0.4: - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -7516,8 +7467,6 @@ snapshots: dependencies: has-symbols: 1.0.3 - has-unicode@2.0.1: {} - hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -7596,13 +7545,6 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.5: - dependencies: - agent-base: 7.1.1 - debug: 4.4.0 - transitivePeerDependencies: - - supports-color - https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 @@ -8346,8 +8288,6 @@ snapshots: lowercase-keys@2.0.0: {} - lru-cache@10.2.0: {} - lru-cache@10.4.3: {} lru-cache@11.0.0: {} @@ -8719,23 +8659,6 @@ snapshots: dependencies: semver: 7.7.2 - node-gyp@9.4.1: - dependencies: - env-paths: 2.2.1 - exponential-backoff: 3.1.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - make-fetch-happen: 10.2.1 - nopt: 6.0.0 - npmlog: 6.0.2 - rimraf: 3.0.2 - semver: 7.7.2 - tar: 6.2.1 - which: 2.0.2 - transitivePeerDependencies: - - bluebird - - supports-color - node-int64@0.4.0: {} node-releases@2.0.18: {} @@ -8752,13 +8675,6 @@ snapshots: dependencies: path-key: 3.1.1 - npmlog@6.0.2: - dependencies: - are-we-there-yet: 3.0.1 - console-control-strings: 1.1.0 - gauge: 4.0.4 - set-blocking: 2.0.0 - nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -8847,7 +8763,7 @@ snapshots: path-scurry@1.11.1: dependencies: - lru-cache: 10.2.0 + lru-cache: 10.4.3 minipass: 7.1.2 path-scurry@2.0.0: @@ -9105,6 +9021,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + proc-log@2.0.1: {} + process-nextick-args@2.0.1: {} progress@2.0.3: {} @@ -9370,6 +9288,8 @@ snapshots: semver-compare@1.0.0: optional: true + semver@5.7.2: {} + semver@6.3.1: {} semver@7.7.2: {} @@ -9383,8 +9303,6 @@ snapshots: dependencies: randombytes: 2.1.0 - set-blocking@2.0.0: {} - set-cookie-parser@2.7.1: {} shallow-clone@3.0.1: @@ -9645,6 +9563,10 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + tiny-async-pool@1.3.0: + dependencies: + semver: 5.7.2 + tiny-typed-emitter@2.1.0: {} tinyglobby@0.2.12: @@ -9971,10 +9893,6 @@ snapshots: dependencies: isexe: 2.0.0 - wide-align@1.1.5: - dependencies: - string-width: 4.2.3 - wildcard@2.0.1: {} wordwrap@1.0.0: {} From c8501034038547de976d274fa2026adffe7bdfc1 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 20 Aug 2025 18:39:56 -0700 Subject: [PATCH 02/42] docs: update badges --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b83e4465c..979d26904 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,13 @@ For more information, see [LICENSE](LICENSE). [brew]: https://brew.sh/ [homebrew-cask]: https://formulae.brew.sh/cask/gitify -[coverage-badge]: https://img.shields.io/sonar/coverage/gitify-app_gitify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarcloud +[coverage-badge]: https://img.shields.io/sonar/coverage/gitify-app_gitify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarqubecloud [coverage]: https://sonarcloud.io/summary/new_code?id=gitify-app_gitify -[quality-badge]: https://img.shields.io/sonar/quality_gate/gitify-app_gitify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarcloud +[quality-badge]: https://img.shields.io/sonar/quality_gate/gitify-app_gitify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarqubecloud [quality]: https://sonarcloud.io/summary/new_code?id=gitify-app_gitify -[ci-workflow-badge]: https://github.com/gitify-app/gitify/actions/workflows/ci.yml/badge.svg -[release-workflow-badge]: https://github.com/gitify-app/gitify/actions/workflows/release.yml/badge.svg +[ci-workflow-badge]: https://img.shields.io/github/actions/workflow/status/gitify-app/gitify/ci.yml?logo=github&label=CI +[release-workflow-badge]: https://img.shields.io/github/actions/workflow/status/gitify-app/gitify/release.yml?logo=github&label=Release [downloads-total-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/total?label=downloads@all&logo=github [downloads-latest-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/latest/total?logo=github [contributors-badge]: https://img.shields.io/github/contributors/gitify-app/gitify?logo=github @@ -58,4 +58,4 @@ For more information, see [LICENSE](LICENSE). [github-release-badge]: https://img.shields.io/github/v/release/gitify-app/gitify?logo=github [homebrew-cask-badge]: https://img.shields.io/homebrew/cask/v/gitify?logo=homebrew [renovate]: https://renovatebot.com/ -[renovate-badge]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg?logo=renovate +[renovate-badge]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg?logo=renovate&logoColor=white From 58fb17ab5939a247ff855256e74385d0804c0161 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 20 Aug 2025 18:41:57 -0700 Subject: [PATCH 03/42] docs: update badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 979d26904..e860a5a7e 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,6 @@ For more information, see [LICENSE](LICENSE). [license]: LICENSE [license-badge]: https://img.shields.io/github/license/gitify-app/gitify?logo=github [github-release-badge]: https://img.shields.io/github/v/release/gitify-app/gitify?logo=github -[homebrew-cask-badge]: https://img.shields.io/homebrew/cask/v/gitify?logo=homebrew +[homebrew-cask-badge]: https://img.shields.io/homebrew/cask/v/gitify?logo=homebrew&logoColor=white [renovate]: https://renovatebot.com/ [renovate-badge]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg?logo=renovate&logoColor=white From d6867092a31db184f50703602f69180970efa430 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Thu, 21 Aug 2025 17:37:22 -0400 Subject: [PATCH 04/42] chore: tailwind migrate (#2169) Signed-off-by: Adam Setch --- src/renderer/components/notifications/NotificationRow.tsx | 2 +- .../components/notifications/RepositoryNotifications.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/notifications/NotificationRow.tsx b/src/renderer/components/notifications/NotificationRow.tsx index dda3f6547..6b2108728 100644 --- a/src/renderer/components/notifications/NotificationRow.tsx +++ b/src/renderer/components/notifications/NotificationRow.tsx @@ -86,7 +86,7 @@ export const NotificationRow: FC = ({ 'pl-3 pr-1 py-1.5', 'text-gitify-font border-gitify-notification-border hover:bg-gitify-notification-hover', (isAnimated || animateExit) && - 'translate-x-full opacity-0 transition duration-[350ms] ease-in-out', + 'translate-x-full opacity-0 transition duration-350 ease-in-out', (isRead || showAsRead) && Opacity.READ, )} > diff --git a/src/renderer/components/notifications/RepositoryNotifications.tsx b/src/renderer/components/notifications/RepositoryNotifications.tsx index 31f4e07d4..2d7e5c488 100644 --- a/src/renderer/components/notifications/RepositoryNotifications.tsx +++ b/src/renderer/components/notifications/RepositoryNotifications.tsx @@ -62,7 +62,7 @@ export const RepositoryNotifications: FC = ({ 'group pr-1 py-0.5', 'bg-gitify-repository', animateExit && - 'translate-x-full opacity-0 transition duration-[350ms] ease-in-out', + 'translate-x-full opacity-0 transition duration-350 ease-in-out', showAsRead && Opacity.READ, )} onClick={actionToggleRepositoryNotifications} From 5179752c592e01065b8ed7177289b8d0650786be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:56:37 -0400 Subject: [PATCH 05/42] chore(deps): update @testing-library/jest-dom to v6.8.0 (#2170) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index a34543a81..4a6e73ffd 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "@primer/primitives": "11.1.0", "@primer/react": "36.27.0", "@tailwindcss/postcss": "4.1.12", - "@testing-library/jest-dom": "6.7.0", + "@testing-library/jest-dom": "6.8.0", "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "30.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8afb7e3a7..45d649587 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,8 +55,8 @@ importers: specifier: 4.1.12 version: 4.1.12 '@testing-library/jest-dom': - specifier: 6.7.0 - version: 6.7.0 + specifier: 6.8.0 + version: 6.8.0 '@testing-library/react': specifier: 16.3.0 version: 16.3.0(@testing-library/dom@10.0.0)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -965,8 +965,8 @@ packages: resolution: {integrity: sha512-PmJPnogldqoVFf+EwbHvbBJ98MmqASV8kLrBYgsDNxQcFMeIS7JFL48sfyXvuMtgmWO/wMhh25odr+8VhDmn4g==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.7.0': - resolution: {integrity: sha512-RI2e97YZ7MRa+vxP4UUnMuMFL2buSsf0ollxUbTgrbPLKhMn8KVTx7raS6DYjC7v1NDVrioOvaShxsguLNISCA==} + '@testing-library/jest-dom@6.8.0': + resolution: {integrity: sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} '@testing-library/react@16.3.0': @@ -5703,7 +5703,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.7.0': + '@testing-library/jest-dom@6.8.0': dependencies: '@adobe/css-tools': 4.4.2 aria-query: 5.3.0 From a38a2c972f211cb6aafd4de5b3da8c4740421e74 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Fri, 22 Aug 2025 08:02:44 -0400 Subject: [PATCH 06/42] refactor: use `@primer/css` directly (#2171) refactor: use @primer/css directly Signed-off-by: Adam Setch --- package.json | 1 + pnpm-lock.yaml | 13 +++++++++++++ src/renderer/App.css | 31 +------------------------------ 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 4a6e73ffd..28f1c96af 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "@biomejs/biome": "2.2.0", "@discordapp/twemoji": "16.0.1", "@electron/notarize": "3.0.2", + "@primer/css": "22.0.2", "@primer/octicons-react": "19.15.5", "@primer/primitives": "11.1.0", "@primer/react": "36.27.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45d649587..72248633b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: '@electron/notarize': specifier: 3.0.2 version: 3.0.2 + '@primer/css': + specifier: 22.0.2 + version: 22.0.2(@primer/primitives@11.1.0) '@primer/octicons-react': specifier: 19.15.5 version: 19.15.5(react@19.1.1) @@ -775,6 +778,12 @@ packages: '@primer/behaviors@1.8.0': resolution: {integrity: sha512-ZUfhWVY4ZBKc2Fh3fIa2Qwwa3SnOi914lY5wcmN+UNtsBxeXsjWNwpohJbwRwWZm+nJ3C1n9qJFWpHuBlDVU1A==} + '@primer/css@22.0.2': + resolution: {integrity: sha512-FfXd1ga05oewKjDRi9cPmy7BSnb/6QOjTIxAtDj94Hoyk+qJxHhgvhbcnZwBfL3WKP6HeUT3PnsT9k+43Bmg3Q==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@primer/primitives': 10.x || 11.x + '@primer/live-region-element@0.7.1': resolution: {integrity: sha512-9uQCeBCb3wefz3kJNSo+PECc7T7TNB3k22JUdHY08Zlv9bd1rtsQgpazM5umcbZQrACzGbgufAfdbhGUBXI3jA==} @@ -5490,6 +5499,10 @@ snapshots: '@primer/behaviors@1.8.0': {} + '@primer/css@22.0.2(@primer/primitives@11.1.0)': + dependencies: + '@primer/primitives': 11.1.0 + '@primer/live-region-element@0.7.1': dependencies: '@lit-labs/ssr-dom-shim': 1.2.1 diff --git a/src/renderer/App.css b/src/renderer/App.css index dff880d28..dca3a8cb0 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -2,36 +2,7 @@ @import "https://github.com/tailwindcss"; /** GitHub Primer Design System */ -/* Size & Typography */ -@import "https://github.com/@primer/primitives/dist/css/primitives.css"; - -/* Base */ -@import "https://github.com/@primer/primitives/dist/css/base/motion/motion.css"; -@import "https://github.com/@primer/primitives/dist/css/base/size/size.css"; -@import "https://github.com/@primer/primitives/dist/css/base/typography/typography.css"; - -/* Functional */ -@import "https://github.com/@primer/primitives/dist/css/functional/size/border.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/size/breakpoints.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/size/size.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/size/viewport.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/typography/typography.css"; - -/* Themes and Colors */ -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-colorblind-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-colorblind.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-dimmed-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-dimmed.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-tritanopia-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-tritanopia.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-colorblind-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-colorblind.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-tritanopia-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-tritanopia.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light.css"; +@import "https://github.com/@primer/css/dist/primer.css"; /** Tailwind CSS Configuration */ @config '../../tailwind.config.ts'; From 9c3700a4a59c53a276c5bc48d2bcc16c8853f048 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Fri, 22 Aug 2025 08:57:24 -0400 Subject: [PATCH 07/42] chore(vscode): css file association with tailwindcss Signed-off-by: Adam Setch --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 308fbc6eb..95b1876a3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,9 @@ "source.fixAll.biome": "explicit" }, "editor.defaultFormatter": "biomejs.biome", + "files.associations": { + "*.css": "tailwindcss" + }, "[typescript]": { "editor.defaultFormatter": "biomejs.biome" }, From 9a6e38334df0f8e922122a0cca5ac6d726818349 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 10:16:55 -0400 Subject: [PATCH 08/42] chore(deps): update amannn/action-semantic-pull-request action to v6.1.1 (#2173) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/triage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 2f3f18126..bcf3962a8 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -22,7 +22,7 @@ jobs: name: Validate PR title runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@7f33ba792281b034f64e96f4c0b5496782dd3b37 # v6.1.0 + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 5405124e0c79f8c939944764a591e36636e7bf89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Valli=C3=A8res-Lagac=C3=A9?= Date: Tue, 26 Aug 2025 17:14:34 -0400 Subject: [PATCH 09/42] feat: added the organization filter (#2174) * added the organization filter * case insensitive orgs, moved filter code, removed detailed notif warning * small tweaks Signed-off-by: Adam Setch --------- Signed-off-by: Adam Setch Co-authored-by: Adam Setch --- src/renderer/__mocks__/state-mocks.ts | 2 + .../filters/OrganizationFilter.test.tsx | 130 ++++++++++ .../components/filters/OrganizationFilter.tsx | 206 ++++++++++++++++ src/renderer/context/App.tsx | 4 + src/renderer/routes/Filters.tsx | 2 + .../__snapshots__/Filters.test.tsx.snap | 224 ++++++++++++++++++ src/renderer/types.ts | 5 + .../notifications/filters/filter.test.ts | 86 +++++++ .../utils/notifications/filters/filter.ts | 33 +++ .../utils/notifications/filters/index.ts | 1 + .../filters/organizations.test.ts | 46 ++++ .../notifications/filters/organizations.ts | 20 ++ 12 files changed, 759 insertions(+) create mode 100644 src/renderer/components/filters/OrganizationFilter.test.tsx create mode 100644 src/renderer/components/filters/OrganizationFilter.tsx create mode 100644 src/renderer/utils/notifications/filters/organizations.test.ts create mode 100644 src/renderer/utils/notifications/filters/organizations.ts diff --git a/src/renderer/__mocks__/state-mocks.ts b/src/renderer/__mocks__/state-mocks.ts index 295c420a8..a05dae93d 100644 --- a/src/renderer/__mocks__/state-mocks.ts +++ b/src/renderer/__mocks__/state-mocks.ts @@ -111,6 +111,8 @@ const mockFilters: FilterSettingsState = { filterUserTypes: [], filterIncludeHandles: [], filterExcludeHandles: [], + filterIncludeOrganizations: [], + filterExcludeOrganizations: [], filterSubjectTypes: [], filterStates: [], filterReasons: [], diff --git a/src/renderer/components/filters/OrganizationFilter.test.tsx b/src/renderer/components/filters/OrganizationFilter.test.tsx new file mode 100644 index 000000000..ab0aff667 --- /dev/null +++ b/src/renderer/components/filters/OrganizationFilter.test.tsx @@ -0,0 +1,130 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { mockSettings } from '../../__mocks__/state-mocks'; +import { AppContext } from '../../context/App'; +import type { SettingsState } from '../../types'; +import { OrganizationFilter } from './OrganizationFilter'; + +const mockUpdateFilter = jest.fn(); + +describe('components/filters/OrganizationFilter.tsx', () => { + beforeEach(() => { + mockUpdateFilter.mockReset(); + }); + + it('should render itself & its children', () => { + const props = { + updateFilter: mockUpdateFilter, + settings: mockSettings, + }; + + render( + + + , + ); + + expect(screen.getByText('Organizations')).toBeInTheDocument(); + expect(screen.getByText('Include:')).toBeInTheDocument(); + expect(screen.getByText('Exclude:')).toBeInTheDocument(); + }); + + describe('Include organizations', () => { + it('should handle organization includes', async () => { + const props = { + updateFilter: mockUpdateFilter, + settings: mockSettings, + }; + + render( + + + , + ); + + await userEvent.type( + screen.getByTitle('Include organizations'), + 'microsoft{enter}', + ); + + expect(mockUpdateFilter).toHaveBeenCalledWith( + 'filterIncludeOrganizations', + 'microsoft', + true, + ); + }); + + it('should not allow duplicate include organizations', async () => { + const props = { + updateFilter: mockUpdateFilter, + settings: { + ...mockSettings, + filterIncludeOrganizations: ['microsoft'], + } as SettingsState, + }; + + render( + + + , + ); + + await userEvent.type( + screen.getByTitle('Include organizations'), + 'microsoft{enter}', + ); + + expect(mockUpdateFilter).toHaveBeenCalledTimes(0); + }); + }); + + describe('Exclude organizations', () => { + it('should handle organization excludes', async () => { + const props = { + updateFilter: mockUpdateFilter, + settings: mockSettings, + }; + + render( + + + , + ); + + await userEvent.type( + screen.getByTitle('Exclude organizations'), + 'github{enter}', + ); + + expect(mockUpdateFilter).toHaveBeenCalledWith( + 'filterExcludeOrganizations', + 'github', + true, + ); + }); + + it('should not allow duplicate exclude organizations', async () => { + const props = { + updateFilter: mockUpdateFilter, + settings: { + ...mockSettings, + filterExcludeOrganizations: ['github'], + } as SettingsState, + }; + + render( + + + , + ); + + await userEvent.type( + screen.getByTitle('Exclude organizations'), + 'github{enter}', + ); + + expect(mockUpdateFilter).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/src/renderer/components/filters/OrganizationFilter.tsx b/src/renderer/components/filters/OrganizationFilter.tsx new file mode 100644 index 000000000..369087f06 --- /dev/null +++ b/src/renderer/components/filters/OrganizationFilter.tsx @@ -0,0 +1,206 @@ +import { type FC, useContext, useEffect, useState } from 'react'; + +import { + CheckCircleFillIcon, + NoEntryFillIcon, + OrganizationIcon, +} from '@primer/octicons-react'; +import { Box, Stack, Text, TextInputWithTokens } from '@primer/react'; + +import { AppContext } from '../../context/App'; +import { IconColor, type Organization } from '../../types'; +import { + hasExcludeOrganizationFilters, + hasIncludeOrganizationFilters, +} from '../../utils/notifications/filters/organizations'; +import { Tooltip } from '../fields/Tooltip'; +import { Title } from '../primitives/Title'; + +type InputToken = { + id: number; + text: string; +}; + +const tokenEvents = ['Enter', 'Tab', ' ', ',']; + +export const OrganizationFilter: FC = () => { + const { updateFilter, settings } = useContext(AppContext); + + // biome-ignore lint/correctness/useExhaustiveDependencies: we only want to run this effect on organization filter changes + useEffect(() => { + if (!hasIncludeOrganizationFilters(settings)) { + setIncludeOrganizations([]); + } + + if (!hasExcludeOrganizationFilters(settings)) { + setExcludeOrganizations([]); + } + }, [ + settings.filterIncludeOrganizations, + settings.filterExcludeOrganizations, + ]); + + const mapValuesToTokens = (values: string[]): InputToken[] => { + return values.map((value, index) => ({ + id: index, + text: value, + })); + }; + + const [includeOrganizations, setIncludeOrganizations] = useState< + InputToken[] + >(mapValuesToTokens(settings.filterIncludeOrganizations)); + + const addIncludeOrganizationsToken = ( + event: + | React.KeyboardEvent + | React.FocusEvent, + ) => { + const value = (event.target as HTMLInputElement).value.trim(); + + if ( + value.length > 0 && + !includeOrganizations.some((v) => v.text === value) + ) { + setIncludeOrganizations([ + ...includeOrganizations, + { id: includeOrganizations.length, text: value }, + ]); + updateFilter('filterIncludeOrganizations', value as Organization, true); + + (event.target as HTMLInputElement).value = ''; + } + }; + + const removeIncludeOrganizationToken = (tokenId: string | number) => { + const value = + includeOrganizations.find((v) => v.id === tokenId)?.text || ''; + updateFilter('filterIncludeOrganizations', value as Organization, false); + + setIncludeOrganizations( + includeOrganizations.filter((v) => v.id !== tokenId), + ); + }; + + const includeOrganizationsKeyDown = ( + event: React.KeyboardEvent, + ) => { + if (tokenEvents.includes(event.key)) { + addIncludeOrganizationsToken(event); + } + }; + + const [excludeOrganizations, setExcludeOrganizations] = useState< + InputToken[] + >(mapValuesToTokens(settings.filterExcludeOrganizations)); + + const addExcludeOrganizationsToken = ( + event: + | React.KeyboardEvent + | React.FocusEvent, + ) => { + const value = (event.target as HTMLInputElement).value.trim(); + + if ( + value.length > 0 && + !excludeOrganizations.some((v) => v.text === value) + ) { + setExcludeOrganizations([ + ...excludeOrganizations, + { id: excludeOrganizations.length, text: value }, + ]); + updateFilter('filterExcludeOrganizations', value as Organization, true); + + (event.target as HTMLInputElement).value = ''; + } + }; + + const removeExcludeOrganizationToken = (tokenId: string | number) => { + const value = + excludeOrganizations.find((v) => v.id === tokenId)?.text || ''; + updateFilter('filterExcludeOrganizations', value as Organization, false); + + setExcludeOrganizations( + excludeOrganizations.filter((v) => v.id !== tokenId), + ); + }; + + const excludeOrganizationsKeyDown = ( + event: React.KeyboardEvent, + ) => { + if (tokenEvents.includes(event.key)) { + addExcludeOrganizationsToken(event); + } + }; + + return ( +
+ + Organizations + + Filter notifications by organization. + + } + /> + + + + + + + Include: + + + + + + + + + + Exclude: + + + + + +
+ ); +}; diff --git a/src/renderer/context/App.tsx b/src/renderer/context/App.tsx index f1b041d77..2f750c985 100644 --- a/src/renderer/context/App.tsx +++ b/src/renderer/context/App.tsx @@ -104,6 +104,8 @@ export const defaultFilters: FilterSettingsState = { filterUserTypes: [], filterIncludeHandles: [], filterExcludeHandles: [], + filterIncludeOrganizations: [], + filterExcludeOrganizations: [], filterSubjectTypes: [], filterStates: [], filterReasons: [], @@ -193,6 +195,8 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { settings.filterUserTypes, settings.filterIncludeHandles, settings.filterExcludeHandles, + settings.filterIncludeOrganizations, + settings.filterExcludeOrganizations, settings.filterReasons, ]); diff --git a/src/renderer/routes/Filters.tsx b/src/renderer/routes/Filters.tsx index cce5d8d5e..ebd7c9658 100644 --- a/src/renderer/routes/Filters.tsx +++ b/src/renderer/routes/Filters.tsx @@ -3,6 +3,7 @@ import { type FC, useContext } from 'react'; import { FilterIcon, FilterRemoveIcon } from '@primer/octicons-react'; import { Button, Stack, Tooltip } from '@primer/react'; +import { OrganizationFilter } from '../components/filters/OrganizationFilter'; import { ReasonFilter } from '../components/filters/ReasonFilter'; import { StateFilter } from '../components/filters/StateFilter'; import { SubjectTypeFilter } from '../components/filters/SubjectTypeFilter'; @@ -27,6 +28,7 @@ export const FiltersRoute: FC = () => { + diff --git a/src/renderer/routes/__snapshots__/Filters.test.tsx.snap b/src/renderer/routes/__snapshots__/Filters.test.tsx.snap index 672797719..dae095aab 100644 --- a/src/renderer/routes/__snapshots__/Filters.test.tsx.snap +++ b/src/renderer/routes/__snapshots__/Filters.test.tsx.snap @@ -511,6 +511,230 @@ exports[`renderer/routes/Filters.tsx General should render itself & its children +
+
+ +
+
+ +

+ Organizations +

+
+
+
+
+ +
+
+
+
+
+
+ + + Include: + +
+
+ +
+
+ +
+
+
+
+
+
+
+ + + Exclude: + +
+
+ +
+
+ +
+
+
+
+
+
diff --git a/src/renderer/types.ts b/src/renderer/types.ts index 90398e322..9960c43b1 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -34,6 +34,8 @@ export type Link = Branded; export type UserHandle = Branded; +export type Organization = Branded; + export type Status = 'loading' | 'success' | 'error'; export interface Account { @@ -58,6 +60,7 @@ export type FilterValue = | Reason | UserType | UserHandle + | Organization | FilterStateType | SubjectType; @@ -101,6 +104,8 @@ export interface FilterSettingsState { filterUserTypes: UserType[]; filterIncludeHandles: string[]; filterExcludeHandles: string[]; + filterIncludeOrganizations: string[]; + filterExcludeOrganizations: string[]; filterSubjectTypes: SubjectType[]; filterStates: FilterStateType[]; filterReasons: Reason[]; diff --git a/src/renderer/utils/notifications/filters/filter.test.ts b/src/renderer/utils/notifications/filters/filter.test.ts index 4a3e2c5eb..d51a232e5 100644 --- a/src/renderer/utils/notifications/filters/filter.test.ts +++ b/src/renderer/utils/notifications/filters/filter.test.ts @@ -122,6 +122,76 @@ describe('renderer/utils/notifications/filters/filter.ts', () => { expect(result.length).toBe(1); expect(result).toEqual([mockNotifications[1]]); }); + + it('should filter notifications that match include organization', async () => { + // Initialize repository owner structure if it doesn't exist + if (!mockNotifications[0].repository) { + mockNotifications[0].repository = {} as any; + } + if (!mockNotifications[0].repository.owner) { + mockNotifications[0].repository.owner = {} as any; + } + if (!mockNotifications[1].repository) { + mockNotifications[1].repository = {} as any; + } + if (!mockNotifications[1].repository.owner) { + mockNotifications[1].repository.owner = {} as any; + } + + mockNotifications[0].repository.owner.login = 'microsoft'; + mockNotifications[1].repository.owner.login = 'github'; + + // Apply base filtering first (where organization filtering now happens) + let result = filterBaseNotifications(mockNotifications, { + ...mockSettings, + filterIncludeOrganizations: ['microsoft'], + }); + + // Then apply detailed filtering + result = filterDetailedNotifications(result, { + ...mockSettings, + detailedNotifications: true, + filterIncludeOrganizations: ['microsoft'], + }); + + expect(result.length).toBe(1); + expect(result).toEqual([mockNotifications[0]]); + }); + + it('should filter notifications that match exclude organization', async () => { + // Initialize repository owner structure if it doesn't exist + if (!mockNotifications[0].repository) { + mockNotifications[0].repository = {} as any; + } + if (!mockNotifications[0].repository.owner) { + mockNotifications[0].repository.owner = {} as any; + } + if (!mockNotifications[1].repository) { + mockNotifications[1].repository = {} as any; + } + if (!mockNotifications[1].repository.owner) { + mockNotifications[1].repository.owner = {} as any; + } + + mockNotifications[0].repository.owner.login = 'microsoft'; + mockNotifications[1].repository.owner.login = 'github'; + + // Apply base filtering first (where organization filtering now happens) + let result = filterBaseNotifications(mockNotifications, { + ...mockSettings, + filterExcludeOrganizations: ['github'], + }); + + // Then apply detailed filtering + result = filterDetailedNotifications(result, { + ...mockSettings, + detailedNotifications: true, + filterExcludeOrganizations: ['github'], + }); + + expect(result.length).toBe(1); + expect(result).toEqual([mockNotifications[0]]); + }); }); }); @@ -154,6 +224,22 @@ describe('renderer/utils/notifications/filters/filter.ts', () => { expect(hasAnyFiltersSet(settings)).toBe(true); }); + it('non-default organization includes filters', () => { + const settings: SettingsState = { + ...defaultSettings, + filterIncludeOrganizations: ['microsoft'], + }; + expect(hasAnyFiltersSet(settings)).toBe(true); + }); + + it('non-default organization excludes filters', () => { + const settings: SettingsState = { + ...defaultSettings, + filterExcludeOrganizations: ['github'], + }; + expect(hasAnyFiltersSet(settings)).toBe(true); + }); + it('non-default subject type filters', () => { const settings: SettingsState = { ...defaultSettings, diff --git a/src/renderer/utils/notifications/filters/filter.ts b/src/renderer/utils/notifications/filters/filter.ts index 6f2e3c0db..5961dbcc7 100644 --- a/src/renderer/utils/notifications/filters/filter.ts +++ b/src/renderer/utils/notifications/filters/filter.ts @@ -6,8 +6,11 @@ import type { } from '../../../typesGitHub'; import { filterNotificationByHandle, + filterNotificationByOrganization, hasExcludeHandleFilters, + hasExcludeOrganizationFilters, hasIncludeHandleFilters, + hasIncludeOrganizationFilters, reasonFilter, stateFilter, subjectTypeFilter, @@ -21,6 +24,9 @@ export function filterBaseNotifications( return notifications.filter((notification) => { let passesFilters = true; + passesFilters = + passesFilters && passesOrganizationFilters(notification, settings); + if (subjectTypeFilter.hasFilters(settings)) { passesFilters = passesFilters && @@ -65,6 +71,8 @@ export function hasAnyFiltersSet(settings: SettingsState): boolean { userTypeFilter.hasFilters(settings) || hasIncludeHandleFilters(settings) || hasExcludeHandleFilters(settings) || + hasIncludeOrganizationFilters(settings) || + hasExcludeOrganizationFilters(settings) || subjectTypeFilter.hasFilters(settings) || stateFilter.hasFilters(settings) || reasonFilter.hasFilters(settings) @@ -104,6 +112,31 @@ function passesUserFilters( return passesFilters; } +function passesOrganizationFilters( + notification: Notification, + settings: SettingsState, +): boolean { + let passesFilters = true; + + if (hasIncludeOrganizationFilters(settings)) { + passesFilters = + passesFilters && + settings.filterIncludeOrganizations.some((organization) => + filterNotificationByOrganization(notification, organization), + ); + } + + if (hasExcludeOrganizationFilters(settings)) { + passesFilters = + passesFilters && + !settings.filterExcludeOrganizations.some((organization) => + filterNotificationByOrganization(notification, organization), + ); + } + + return passesFilters; +} + function passesStateFilter( notification: Notification, settings: SettingsState, diff --git a/src/renderer/utils/notifications/filters/index.ts b/src/renderer/utils/notifications/filters/index.ts index c2a1e0550..422bd4e12 100644 --- a/src/renderer/utils/notifications/filters/index.ts +++ b/src/renderer/utils/notifications/filters/index.ts @@ -1,4 +1,5 @@ export * from './handles'; +export * from './organizations'; export * from './reason'; export * from './state'; export * from './subjectType'; diff --git a/src/renderer/utils/notifications/filters/organizations.test.ts b/src/renderer/utils/notifications/filters/organizations.test.ts new file mode 100644 index 000000000..d520e1653 --- /dev/null +++ b/src/renderer/utils/notifications/filters/organizations.test.ts @@ -0,0 +1,46 @@ +import { mockSettings } from '../../../__mocks__/state-mocks'; +import type { Notification } from '../../../typesGitHub'; +import { + filterNotificationByOrganization, + hasExcludeOrganizationFilters, + hasIncludeOrganizationFilters, +} from './organizations'; + +describe('utils/notifications/filters/organizations.ts', () => { + it('should check if include organization filters exist', () => { + const settingsWithInclude = { + ...mockSettings, + filterIncludeOrganizations: ['microsoft'], + }; + + expect(hasIncludeOrganizationFilters(mockSettings)).toBe(false); + expect(hasIncludeOrganizationFilters(settingsWithInclude)).toBe(true); + }); + + it('should check if exclude organization filters exist', () => { + const settingsWithExclude = { + ...mockSettings, + filterExcludeOrganizations: ['github'], + }; + + expect(hasExcludeOrganizationFilters(mockSettings)).toBe(false); + expect(hasExcludeOrganizationFilters(settingsWithExclude)).toBe(true); + }); + + it('should filter notification by organization', () => { + const notification = { + repository: { + owner: { + login: 'microsoft', + }, + }, + } as Notification; + + expect(filterNotificationByOrganization(notification, 'microsoft')).toBe( + true, + ); + expect(filterNotificationByOrganization(notification, 'github')).toBe( + false, + ); + }); +}); diff --git a/src/renderer/utils/notifications/filters/organizations.ts b/src/renderer/utils/notifications/filters/organizations.ts new file mode 100644 index 000000000..5084fa574 --- /dev/null +++ b/src/renderer/utils/notifications/filters/organizations.ts @@ -0,0 +1,20 @@ +import type { SettingsState } from '../../../types'; +import type { Notification } from '../../../typesGitHub'; + +export function hasIncludeOrganizationFilters(settings: SettingsState) { + return settings.filterIncludeOrganizations.length > 0; +} + +export function hasExcludeOrganizationFilters(settings: SettingsState) { + return settings.filterExcludeOrganizations.length > 0; +} + +export function filterNotificationByOrganization( + notification: Notification, + organizationName: string, +): boolean { + return ( + notification.repository.owner.login.toLowerCase() === + organizationName.toLowerCase() + ); +} From cac7b4d4666eee280f5a11a59bf30a42143a1d03 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Tue, 26 Aug 2025 18:27:21 -0400 Subject: [PATCH 10/42] refactor: extract defaults (#2175) Signed-off-by: Adam Setch --- .../components/settings/SystemSettings.tsx | 3 +- src/renderer/context/App.test.tsx | 3 +- src/renderer/context/App.tsx | 82 +++---------------- src/renderer/context/defaults.ts | 64 +++++++++++++++ src/renderer/utils/comms.ts | 2 +- .../notifications/filters/filter.test.ts | 2 +- .../utils/notifications/native.test.ts | 2 +- 7 files changed, 83 insertions(+), 75 deletions(-) create mode 100644 src/renderer/context/defaults.ts diff --git a/src/renderer/components/settings/SystemSettings.tsx b/src/renderer/components/settings/SystemSettings.tsx index ab69ff850..8d53a7025 100644 --- a/src/renderer/components/settings/SystemSettings.tsx +++ b/src/renderer/components/settings/SystemSettings.tsx @@ -12,7 +12,8 @@ import { import { APPLICATION } from '../../../shared/constants'; import { isLinux, isMacOS } from '../../../shared/platform'; -import { AppContext, defaultSettings } from '../../context/App'; +import { AppContext } from '../../context/App'; +import { defaultSettings } from '../../context/defaults'; import { OpenPreference } from '../../types'; import { Constants } from '../../utils/constants'; import { Checkbox } from '../fields/Checkbox'; diff --git a/src/renderer/context/App.test.tsx b/src/renderer/context/App.test.tsx index b9f09513f..bca9dd94b 100644 --- a/src/renderer/context/App.test.tsx +++ b/src/renderer/context/App.test.tsx @@ -10,7 +10,8 @@ import * as comms from '../utils/comms'; import { Constants } from '../utils/constants'; import * as notifications from '../utils/notifications/notifications'; import * as storage from '../utils/storage'; -import { AppContext, AppProvider, defaultSettings } from './App'; +import { AppContext, AppProvider } from './App'; +import { defaultSettings } from './defaults'; jest.mock('../hooks/useNotifications'); diff --git a/src/renderer/context/App.tsx b/src/renderer/context/App.tsx index 2f750c985..fe51084a6 100644 --- a/src/renderer/context/App.tsx +++ b/src/renderer/context/App.tsx @@ -14,23 +14,17 @@ import { useTheme } from '@primer/react'; import { namespacedEvent } from '../../shared/events'; import { useInterval } from '../hooks/useInterval'; import { useNotifications } from '../hooks/useNotifications'; -import { - type Account, - type AccountNotifications, - type AppearanceSettingsState, - type AuthState, - type FilterSettingsState, - type FilterValue, - type GitifyError, - GroupBy, - type NotificationSettingsState, - OpenPreference, - type SettingsState, - type SettingsValue, - type Status, - type SystemSettingsState, - Theme, - type Token, +import type { + Account, + AccountNotifications, + AuthState, + FilterSettingsState, + FilterValue, + GitifyError, + SettingsState, + SettingsValue, + Status, + Token, } from '../types'; import type { Notification } from '../typesGitHub'; import { headNotifications } from '../utils/api/client'; @@ -64,59 +58,7 @@ import { mapThemeModeToColorScheme, } from '../utils/theme'; import { zoomPercentageToLevel } from '../utils/zoom'; - -export const defaultAuth: AuthState = { - accounts: [], -}; - -const defaultAppearanceSettings: AppearanceSettingsState = { - theme: Theme.SYSTEM, - increaseContrast: false, - zoomPercentage: 100, - showAccountHeader: false, - wrapNotificationTitle: false, -}; - -const defaultNotificationSettings: NotificationSettingsState = { - groupBy: GroupBy.REPOSITORY, - fetchAllNotifications: true, - detailedNotifications: true, - showPills: true, - showNumber: true, - participating: false, - markAsDoneOnOpen: false, - markAsDoneOnUnsubscribe: false, - delayNotificationState: false, -}; - -const defaultSystemSettings: SystemSettingsState = { - openLinks: OpenPreference.FOREGROUND, - keyboardShortcut: true, - showNotificationsCountInTray: true, - showNotifications: true, - playSound: true, - notificationVolume: 20, - useAlternateIdleIcon: false, - openAtStartup: false, -}; - -export const defaultFilters: FilterSettingsState = { - filterUserTypes: [], - filterIncludeHandles: [], - filterExcludeHandles: [], - filterIncludeOrganizations: [], - filterExcludeOrganizations: [], - filterSubjectTypes: [], - filterStates: [], - filterReasons: [], -}; - -export const defaultSettings: SettingsState = { - ...defaultAppearanceSettings, - ...defaultNotificationSettings, - ...defaultSystemSettings, - ...defaultFilters, -}; +import { defaultAuth, defaultFilters, defaultSettings } from './defaults'; interface AppContextState { auth: AuthState; diff --git a/src/renderer/context/defaults.ts b/src/renderer/context/defaults.ts new file mode 100644 index 000000000..c65402a01 --- /dev/null +++ b/src/renderer/context/defaults.ts @@ -0,0 +1,64 @@ +import { + type AppearanceSettingsState, + type AuthState, + type FilterSettingsState, + GroupBy, + type NotificationSettingsState, + OpenPreference, + type SettingsState, + type SystemSettingsState, + Theme, +} from '../types'; + +export const defaultAuth: AuthState = { + accounts: [], +}; + +const defaultAppearanceSettings: AppearanceSettingsState = { + theme: Theme.SYSTEM, + increaseContrast: false, + zoomPercentage: 100, + showAccountHeader: false, + wrapNotificationTitle: false, +}; + +const defaultNotificationSettings: NotificationSettingsState = { + groupBy: GroupBy.REPOSITORY, + fetchAllNotifications: true, + detailedNotifications: true, + showPills: true, + showNumber: true, + participating: false, + markAsDoneOnOpen: false, + markAsDoneOnUnsubscribe: false, + delayNotificationState: false, +}; + +const defaultSystemSettings: SystemSettingsState = { + openLinks: OpenPreference.FOREGROUND, + keyboardShortcut: true, + showNotificationsCountInTray: true, + showNotifications: true, + playSound: true, + notificationVolume: 20, + useAlternateIdleIcon: false, + openAtStartup: false, +}; + +export const defaultFilters: FilterSettingsState = { + filterUserTypes: [], + filterIncludeHandles: [], + filterExcludeHandles: [], + filterIncludeOrganizations: [], + filterExcludeOrganizations: [], + filterSubjectTypes: [], + filterStates: [], + filterReasons: [], +}; + +export const defaultSettings: SettingsState = { + ...defaultAppearanceSettings, + ...defaultNotificationSettings, + ...defaultSystemSettings, + ...defaultFilters, +}; diff --git a/src/renderer/utils/comms.ts b/src/renderer/utils/comms.ts index 900e0fae3..6476727e1 100644 --- a/src/renderer/utils/comms.ts +++ b/src/renderer/utils/comms.ts @@ -1,7 +1,7 @@ import { ipcRenderer, shell } from 'electron'; import { namespacedEvent } from '../../shared/events'; -import { defaultSettings } from '../context/App'; +import { defaultSettings } from '../context/defaults'; import { type Link, OpenPreference } from '../types'; import { Constants } from './constants'; import { loadState } from './storage'; diff --git a/src/renderer/utils/notifications/filters/filter.test.ts b/src/renderer/utils/notifications/filters/filter.test.ts index d51a232e5..07bd76bfb 100644 --- a/src/renderer/utils/notifications/filters/filter.test.ts +++ b/src/renderer/utils/notifications/filters/filter.test.ts @@ -1,6 +1,6 @@ import { partialMockNotification } from '../../../__mocks__/partial-mocks'; import { mockSettings } from '../../../__mocks__/state-mocks'; -import { defaultSettings } from '../../../context/App'; +import { defaultSettings } from '../../../context/defaults'; import type { Link, SettingsState } from '../../../types'; import { filterBaseNotifications, diff --git a/src/renderer/utils/notifications/native.test.ts b/src/renderer/utils/notifications/native.test.ts index 3edebd3e7..e075452e1 100644 --- a/src/renderer/utils/notifications/native.test.ts +++ b/src/renderer/utils/notifications/native.test.ts @@ -3,7 +3,7 @@ import { mockSingleAccountNotifications, } from '../../__mocks__/notifications-mocks'; import { mockAuth } from '../../__mocks__/state-mocks'; -import { defaultSettings } from '../../context/App'; +import { defaultSettings } from '../../context/defaults'; import type { SettingsState } from '../../types'; import { mockGitHubNotifications } from '../api/__mocks__/response-mocks'; import * as comms from '../comms'; From 1c48ade68576464f85bc18605b97c6b780be770a Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 27 Aug 2025 07:22:48 -0400 Subject: [PATCH 11/42] fix: remove axios default headers (#2177) Signed-off-by: Adam Setch --- .../api/__snapshots__/client.test.ts.snap | 118 ------------------ .../api/__snapshots__/request.test.ts.snap | 35 ------ src/renderer/utils/api/client.test.ts | 104 +++++++++++---- src/renderer/utils/api/request.test.ts | 30 +++-- src/renderer/utils/api/request.ts | 58 +++++---- 5 files changed, 134 insertions(+), 211 deletions(-) delete mode 100644 src/renderer/utils/api/__snapshots__/client.test.ts.snap delete mode 100644 src/renderer/utils/api/__snapshots__/request.test.ts.snap diff --git a/src/renderer/utils/api/__snapshots__/client.test.ts.snap b/src/renderer/utils/api/__snapshots__/client.test.ts.snap deleted file mode 100644 index 0157f3a70..000000000 --- a/src/renderer/utils/api/__snapshots__/client.test.ts.snap +++ /dev/null @@ -1,118 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renderer/utils/api/client.ts getAuthenticatedUser should fetch authenticated user - enterprise 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts getAuthenticatedUser should fetch authenticated user - github 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts headNotifications should fetch notifications head - enterprise 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "no-cache", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts headNotifications should fetch notifications head - github 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "no-cache", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts ignoreNotificationThreadSubscription should ignore notification thread subscription - enterprise 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts ignoreNotificationThreadSubscription should ignore notification thread subscription - github 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts listNotificationsForAuthenticatedUser should list notifications for user - github cloud - fetchAllNotifications false 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "no-cache", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts listNotificationsForAuthenticatedUser should list notifications for user - github cloud - fetchAllNotifications true 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "no-cache", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts listNotificationsForAuthenticatedUser should list notifications for user - github enterprise server 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "no-cache", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts markNotificationThreadAsDone should mark notification thread as done - enterprise 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts markNotificationThreadAsDone should mark notification thread as done - github 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts markNotificationThreadAsRead should mark notification thread as read - enterprise 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/client.ts markNotificationThreadAsRead should mark notification thread as read - github 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; diff --git a/src/renderer/utils/api/__snapshots__/request.test.ts.snap b/src/renderer/utils/api/__snapshots__/request.test.ts.snap deleted file mode 100644 index 4af54e921..000000000 --- a/src/renderer/utils/api/__snapshots__/request.test.ts.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`apiRequestAuth should make an authenticated request with the correct parameters 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`apiRequestAuth should make an authenticated request with the correct parameters and default data 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token decrypted", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/request.ts should make a request with the correct parameters 1`] = ` -{ - "Accept": "application/json", - "Cache-Control": "no-cache", - "Content-Type": "application/json", -} -`; - -exports[`renderer/utils/api/request.ts should make a request with the correct parameters and default data 1`] = ` -{ - "Accept": "application/json", - "Cache-Control": "no-cache", - "Content-Type": "application/json", -} -`; diff --git a/src/renderer/utils/api/client.test.ts b/src/renderer/utils/api/client.test.ts index 0edf752db..84d2e7257 100644 --- a/src/renderer/utils/api/client.test.ts +++ b/src/renderer/utils/api/client.test.ts @@ -35,11 +35,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: 'https://api.github.com/user', + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, method: 'GET', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); it('should fetch authenticated user - enterprise', async () => { @@ -47,11 +51,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: 'https://example.com/api/v3/user', + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, method: 'GET', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); }); @@ -61,11 +69,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: 'https://api.github.com/notifications', + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/json', + }, method: 'HEAD', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); it('should fetch notifications head - enterprise', async () => { @@ -73,11 +85,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: 'https://example.com/api/v3/notifications', + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/json', + }, method: 'HEAD', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); }); @@ -95,11 +111,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: 'https://api.github.com/notifications?participating=true', + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/json', + }, method: 'GET', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); it('should list notifications for user - github cloud - fetchAllNotifications false', async () => { @@ -115,11 +135,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: 'https://api.github.com/notifications?participating=true', + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/json', + }, method: 'GET', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); it('should list notifications for user - github enterprise server', async () => { @@ -134,11 +158,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: 'https://github.gitify.io/api/v3/notifications?participating=true', + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/json', + }, method: 'GET', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); }); @@ -152,11 +180,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: `https://api.github.com/notifications/threads/${mockThreadId}`, + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, method: 'PATCH', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); it('should mark notification thread as read - enterprise', async () => { @@ -168,11 +200,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: `https://example.com/api/v3/notifications/threads/${mockThreadId}`, + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, method: 'PATCH', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); }); @@ -186,11 +222,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: `https://api.github.com/notifications/threads/${mockThreadId}`, + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, method: 'DELETE', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); it('should mark notification thread as done - enterprise', async () => { @@ -202,11 +242,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: `https://example.com/api/v3/notifications/threads/${mockThreadId}`, + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, method: 'DELETE', data: {}, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); }); @@ -220,11 +264,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: `https://api.github.com/notifications/threads/${mockThreadId}/subscription`, + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, method: 'PUT', data: { ignored: true }, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); it('should ignore notification thread subscription - enterprise', async () => { @@ -236,11 +284,15 @@ describe('renderer/utils/api/client.ts', () => { expect(axios).toHaveBeenCalledWith({ url: `https://example.com/api/v3/notifications/threads/${mockThreadId}/subscription`, + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, method: 'PUT', data: { ignored: true }, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); }); diff --git a/src/renderer/utils/api/request.test.ts b/src/renderer/utils/api/request.test.ts index fcc1785f9..c759c6b1e 100644 --- a/src/renderer/utils/api/request.test.ts +++ b/src/renderer/utils/api/request.test.ts @@ -22,9 +22,12 @@ describe('renderer/utils/api/request.ts', () => { method, url, data, + headers: { + Accept: 'application/json', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); it('should make a request with the correct parameters and default data', async () => { @@ -35,9 +38,12 @@ describe('renderer/utils/api/request.ts', () => { method, url, data, + headers: { + Accept: 'application/json', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); }); @@ -57,9 +63,13 @@ describe('apiRequestAuth', () => { method, url, data, + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); it('should make an authenticated request with the correct parameters and default data', async () => { @@ -71,8 +81,12 @@ describe('apiRequestAuth', () => { method, url, data, + headers: { + Accept: 'application/json', + Authorization: 'token decrypted', + 'Cache-Control': '', + 'Content-Type': 'application/json', + }, }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); }); }); diff --git a/src/renderer/utils/api/request.ts b/src/renderer/utils/api/request.ts index ea4b46608..134ea04a0 100644 --- a/src/renderer/utils/api/request.ts +++ b/src/renderer/utils/api/request.ts @@ -4,7 +4,7 @@ import axios, { type Method, } from 'axios'; -import { logError, logWarn } from '../../../shared/logger'; +import { logError } from '../../../shared/logger'; import type { Link, Token } from '../../types'; import { decryptValue } from '../comms'; import { getNextURLFromLinkHeader } from './utils'; @@ -17,15 +17,14 @@ import { getNextURLFromLinkHeader } from './utils'; * @param data * @returns */ -export function apiRequest( +export async function apiRequest( url: Link, method: Method, data = {}, -): AxiosPromise | null { - axios.defaults.headers.common.Accept = 'application/json'; - axios.defaults.headers.common['Content-Type'] = 'application/json'; - axios.defaults.headers.common['Cache-Control'] = 'no-cache'; - return axios({ method, url, data }); +): Promise { + const headers = await getHeaders(url); + + return axios({ method, url, data, headers }); } /** @@ -45,23 +44,10 @@ export async function apiRequestAuth( data = {}, fetchAllRecords = false, ): AxiosPromise | null { - let apiToken = token; - // TODO - Remove this try-catch block in a future release - try { - apiToken = (await decryptValue(token)) as Token; - } catch (err) { - logWarn('apiRequestAuth', 'Token is not yet encrypted'); - } - - axios.defaults.headers.common.Accept = 'application/json'; - axios.defaults.headers.common.Authorization = `token ${apiToken}`; - axios.defaults.headers.common['Content-Type'] = 'application/json'; - axios.defaults.headers.common['Cache-Control'] = shouldRequestWithNoCache(url) - ? 'no-cache' - : ''; + const headers = await getHeaders(url, token); if (!fetchAllRecords) { - return axios({ method, url, data }); + return axios({ method, url, data, headers }); } let response: AxiosResponse | null = null; @@ -71,7 +57,7 @@ export async function apiRequestAuth( let nextUrl: string | null = url; while (nextUrl) { - response = await axios({ method, url: nextUrl, data }); + response = await axios({ method, url: nextUrl, data, headers }); // If no data is returned, break the loop if (!response?.data) { @@ -104,10 +90,34 @@ function shouldRequestWithNoCache(url: string) { const parsedUrl = new URL(url); switch (parsedUrl.pathname) { - case '/notifications': case '/api/v3/notifications': + case '/login/oauth/access_token': + case '/notifications': return true; default: return false; } } + +/** + * Construct headers for API requests + * + * @param username + * @param token + * @returns + */ +async function getHeaders(url: Link, token?: Token) { + const headers: Record = { + Accept: 'application/json', + 'Cache-Control': shouldRequestWithNoCache(url) ? 'no-cache' : '', + 'Content-Type': 'application/json', + }; + + if (token) { + const decryptedToken = (await decryptValue(token)) as Token; + + headers.Authorization = `token ${decryptedToken}`; + } + + return headers; +} From ef3811b0ce36e4300ef59c33b2ff1b6bbfd7ff7a Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 27 Aug 2025 08:21:59 -0400 Subject: [PATCH 12/42] feat: auto updates for all platforms (#2178) * feat: auto updates for all platforms Signed-off-by: Adam Setch * feat: auto updates for all platforms Signed-off-by: Adam Setch --------- Signed-off-by: Adam Setch --- package.json | 3 +- pnpm-lock.yaml | 23 ---- src/main/index.ts | 15 +-- src/main/updater.test.ts | 229 +++++++++++++++++++++++++++++++++++++++ src/main/updater.ts | 89 ++++++++++++--- src/shared/constants.ts | 2 + 6 files changed, 311 insertions(+), 50 deletions(-) create mode 100644 src/main/updater.test.ts diff --git a/package.json b/package.json index 28f1c96af..d1e774d53 100644 --- a/package.json +++ b/package.json @@ -73,8 +73,7 @@ "menubar": "9.5.1", "react": "19.1.1", "react-dom": "19.1.1", - "react-router-dom": "7.8.1", - "update-electron-app": "3.1.1" + "react-router-dom": "7.8.1" }, "devDependencies": { "@biomejs/biome": "2.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72248633b..2f61fa704 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,6 @@ importers: react-router-dom: specifier: 7.8.1 version: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - update-electron-app: - specifier: 3.1.1 - version: 3.1.1 devDependencies: '@biomejs/biome': specifier: 2.2.0 @@ -2428,9 +2425,6 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - github-url-to-object@4.0.6: - resolution: {integrity: sha512-NaqbYHMUAlPcmWFdrAB7bcxrNIiiJWJe8s/2+iOc9vlcHlwHqSGrPk+Yi3nu6ebTwgsZEa7igz+NH2vEq3gYwQ==} - glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -2718,9 +2712,6 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-url@1.2.4: - resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -4512,9 +4503,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - update-electron-app@3.1.1: - resolution: {integrity: sha512-7duRr6sYn014tifhKgT/5i8N+6xLzmJVJ8hVtNrHXlIDNP6QbRe6VxZ1hSi2UH5oJPzhor/PH7yKU9em5xjRzQ==} - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -7367,10 +7355,6 @@ snapshots: get-stream@6.0.1: {} - github-url-to-object@4.0.6: - dependencies: - is-url: 1.2.4 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -7666,8 +7650,6 @@ snapshots: is-unicode-supported@0.1.0: {} - is-url@1.2.4: {} - isarray@1.0.0: {} isbinaryfile@4.0.10: {} @@ -9764,11 +9746,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-electron-app@3.1.1: - dependencies: - github-url-to-object: 4.0.6 - ms: 2.1.2 - uri-js@4.4.1: dependencies: punycode: 2.3.1 diff --git a/src/main/index.ts b/src/main/index.ts index 9336e2c8d..f8f73d5a0 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,11 +5,11 @@ import { menubar } from 'menubar'; import { APPLICATION } from '../shared/constants'; import { namespacedEvent } from '../shared/events'; import { logInfo, logWarn } from '../shared/logger'; -import { isLinux, isMacOS, isWindows } from '../shared/platform'; +import { isLinux, isWindows } from '../shared/platform'; import { onFirstRunMaybe } from './first-run'; import { TrayIcons } from './icons'; import MenuBuilder from './menu'; -import Updater from './updater'; +import AppUpdater from './updater'; log.initialize(); @@ -43,20 +43,15 @@ const protocol = process.env.NODE_ENV === 'development' ? 'gitify-dev' : 'gitify'; app.setAsDefaultProtocolClient(protocol); -if (isMacOS() || isWindows()) { - /** - * Electron Auto Updater only supports macOS and Windows - * https://github.com/electron/update-electron-app - */ - const updater = new Updater(mb, menuBuilder); - updater.initialize(); -} +const appUpdater = new AppUpdater(mb, menuBuilder); let shouldUseAlternateIdleIcon = false; app.whenReady().then(async () => { await onFirstRunMaybe(); + appUpdater.start(); + mb.on('ready', () => { mb.app.setAppUserModelId(APPLICATION.ID); diff --git a/src/main/updater.test.ts b/src/main/updater.test.ts new file mode 100644 index 000000000..3ff570ebd --- /dev/null +++ b/src/main/updater.test.ts @@ -0,0 +1,229 @@ +import { dialog } from 'electron'; +import type { Menubar } from 'menubar'; + +import { APPLICATION } from '../shared/constants'; +import { logError, logInfo } from '../shared/logger'; + +jest.mock('../shared/logger', () => ({ + logInfo: jest.fn(), + logError: jest.fn(), +})); + +import MenuBuilder from './menu'; +import AppUpdater from './updater'; + +// Mock electron-updater with an EventEmitter-like interface +type UpdateDownloadedEvent = { releaseName: string }; +type ListenerArgs = UpdateDownloadedEvent | object | undefined; +type Listener = (arg: ListenerArgs) => void; +type ListenerMap = Record; +const listeners: ListenerMap = {}; + +jest.mock('electron-updater', () => ({ + autoUpdater: { + on: jest.fn((event: string, cb: Listener) => { + if (!listeners[event]) listeners[event] = []; + listeners[event].push(cb); + return this; + }), + checkForUpdatesAndNotify: jest.fn().mockResolvedValue(undefined), + quitAndInstall: jest.fn(), + }, +})); + +// Mock electron (dialog + basic Menu API used by MenuBuilder constructor) +jest.mock('electron', () => { + const MenuItem = jest.fn().mockImplementation((opts: unknown) => opts); + return { + dialog: { showMessageBox: jest.fn() }, + MenuItem, + Menu: { buildFromTemplate: jest.fn() }, + shell: { openExternal: jest.fn() }, + }; +}); + +// Utility to emit mocked autoUpdater events +const emit = (event: string, arg?: ListenerArgs) => { + (listeners[event] || []).forEach((cb) => { + cb(arg); + }); +}; + +// Re-import autoUpdater after mocking +import { autoUpdater } from 'electron-updater'; + +describe('main/updater.ts', () => { + let menubar: Menubar; + class TestMenuBuilder extends MenuBuilder { + public setCheckForUpdatesMenuEnabled = jest.fn(); + public setNoUpdateAvailableMenuVisibility = jest.fn(); + public setUpdateAvailableMenuVisibility = jest.fn(); + public setUpdateReadyForInstallMenuVisibility = jest.fn(); + constructor(mb: Menubar) { + super(mb); + } + } + let menuBuilder: TestMenuBuilder; + let updater: AppUpdater; + + beforeEach(() => { + jest.clearAllMocks(); + for (const k of Object.keys(listeners)) delete listeners[k]; + + menubar = { + app: { + isPackaged: true, + // updater.initialize is now only called after app is ready externally + on: jest.fn(), + }, + tray: { setToolTip: jest.fn() }, + } as unknown as Menubar; + + menuBuilder = new TestMenuBuilder(menubar); + updater = new AppUpdater(menubar, menuBuilder); + }); + + describe('update available dialog', () => { + it('shows dialog with expected message and does NOT install when user chooses Later', async () => { + (dialog.showMessageBox as jest.Mock).mockResolvedValue({ response: 1 }); // "Later" + + await updater.start(); + + // Simulate update downloaded event + const releaseName = 'v1.2.3'; + emit('update-downloaded', { releaseName }); + + expect(dialog.showMessageBox).toHaveBeenCalledWith( + expect.objectContaining({ + message: expect.stringContaining( + `${APPLICATION.NAME} ${releaseName} has been downloaded`, + ), + buttons: ['Restart', 'Later'], + }), + ); + expect(autoUpdater.quitAndInstall).not.toHaveBeenCalled(); + // Menu state updates invoked + expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith( + false, + ); + expect( + menuBuilder.setUpdateReadyForInstallMenuVisibility, + ).toHaveBeenCalledWith(true); + }); + + it('invokes quitAndInstall when user clicks Restart', async () => { + (dialog.showMessageBox as jest.Mock).mockResolvedValue({ response: 0 }); // "Restart" + + await updater.start(); + emit('update-downloaded', { releaseName: 'v9.9.9' }); + // Allow then() of showMessageBox promise to resolve + await Promise.resolve(); + + expect(autoUpdater.quitAndInstall).toHaveBeenCalled(); + }); + }); + + describe('update event handlers & scheduling', () => { + it('skips when app is not packaged', async () => { + Object.defineProperty(menubar.app, 'isPackaged', { value: false }); + await updater.start(); + expect(logInfo).toHaveBeenCalledWith( + 'app updater', + 'Skipping updater since app is in development mode', + ); + expect(autoUpdater.checkForUpdatesAndNotify).not.toHaveBeenCalled(); + }); + + it('handles checking-for-update', async () => { + await updater.start(); + emit('checking-for-update'); + expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith( + false, + ); + expect( + menuBuilder.setNoUpdateAvailableMenuVisibility, + ).toHaveBeenCalledWith(false); + }); + + it('handles update-available', async () => { + await updater.start(); + emit('update-available'); + expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith( + true, + ); + expect(menubar.tray.setToolTip).toHaveBeenCalledWith( + expect.stringContaining('A new update is available'), + ); + }); + + it('handles download-progress', async () => { + await updater.start(); + emit('download-progress', { percent: 12.3456 }); + expect(menubar.tray.setToolTip).toHaveBeenCalledWith( + expect.stringContaining('12.35%'), + ); + }); + + it('handles update-not-available', async () => { + await updater.start(); + emit('update-not-available'); + expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith( + true, + ); + expect( + menuBuilder.setNoUpdateAvailableMenuVisibility, + ).toHaveBeenCalledWith(true); + expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith( + false, + ); + expect( + menuBuilder.setUpdateReadyForInstallMenuVisibility, + ).toHaveBeenCalledWith(false); + }); + + it('handles update-cancelled (reset state)', async () => { + await updater.start(); + emit('update-cancelled'); + expect(menubar.tray.setToolTip).toHaveBeenCalledWith(APPLICATION.NAME); + expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith( + true, + ); + }); + + it('handles error (reset + logError)', async () => { + await updater.start(); + const err = new Error('failure'); + emit('error', err); + expect(logError).toHaveBeenCalledWith( + 'auto updater', + 'Error checking for update', + err, + ); + expect(menubar.tray.setToolTip).toHaveBeenCalledWith(APPLICATION.NAME); + }); + + it('performs initial check and schedules periodic checks', async () => { + const originalSetInterval = global.setInterval; + const setIntervalSpy = jest + .spyOn(global, 'setInterval') + .mockImplementation(((fn: () => void) => { + fn(); + return 0 as unknown as NodeJS.Timer; + }) as unknown as typeof setInterval); + try { + await updater.start(); + // initial + immediate scheduled invocation + expect( + (autoUpdater.checkForUpdatesAndNotify as jest.Mock).mock.calls.length, + ).toBe(2); + expect(setIntervalSpy).toHaveBeenCalledWith( + expect.any(Function), + APPLICATION.UPDATE_CHECK_INTERVAL_MS, + ); + } finally { + setIntervalSpy.mockRestore(); + global.setInterval = originalSetInterval; + } + }); + }); +}); diff --git a/src/main/updater.ts b/src/main/updater.ts index 2c80fb4b5..c1f5202a8 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -1,37 +1,64 @@ +import { dialog, type MessageBoxOptions } from 'electron'; import log from 'electron-log'; import { autoUpdater } from 'electron-updater'; import type { Menubar } from 'menubar'; -import { updateElectronApp } from 'update-electron-app'; import { APPLICATION } from '../shared/constants'; import { logError, logInfo } from '../shared/logger'; import type MenuBuilder from './menu'; -export default class Updater { +/** + * Updater class for handling application updates. + * + * Supports scheduled and manual updates for all platforms. + * + * Documentation: https://www.electron.build/auto-update + * + * NOTE: previously used update-electron-app (Squirrel-focused, no Linux + NSIS). electron-updater gives cross-platform support. + * Caller guarantees app is ready before initialize() is invoked. + */ +export default class AppUpdater { private readonly menubar: Menubar; private readonly menuBuilder: MenuBuilder; + private started = false; constructor(menubar: Menubar, menuBuilder: MenuBuilder) { this.menubar = menubar; this.menuBuilder = menuBuilder; + autoUpdater.logger = log; } - initialize(): void { - updateElectronApp({ - updateInterval: '24 hours', - logger: log, - }); + async start(): Promise { + if (this.started) { + return; // idempotent + } + + if (!this.menubar.app.isPackaged) { + logInfo( + 'app updater', + 'Skipping updater since app is in development mode', + ); + return; + } + + logInfo('app updater', 'Starting updater'); + + this.registerListeners(); + await this.performInitialCheck(); + this.schedulePeriodicChecks(); + this.started = true; + } + + private registerListeners() { autoUpdater.on('checking-for-update', () => { logInfo('auto updater', 'Checking for update'); - this.menuBuilder.setCheckForUpdatesMenuEnabled(false); this.menuBuilder.setNoUpdateAvailableMenuVisibility(false); }); autoUpdater.on('update-available', () => { - logInfo('auto updater', 'New update available'); - + logInfo('auto updater', 'Update available'); this.setTooltipWithStatus('A new update is available'); this.menuBuilder.setUpdateAvailableMenuVisibility(true); }); @@ -42,17 +69,16 @@ export default class Updater { ); }); - autoUpdater.on('update-downloaded', () => { + autoUpdater.on('update-downloaded', (event) => { logInfo('auto updater', 'Update downloaded'); - this.setTooltipWithStatus('A new update is ready to install'); this.menuBuilder.setUpdateAvailableMenuVisibility(false); this.menuBuilder.setUpdateReadyForInstallMenuVisibility(true); + this.showUpdateReadyDialog(event.releaseName); }); autoUpdater.on('update-not-available', () => { logInfo('auto updater', 'Update not available'); - this.menuBuilder.setCheckForUpdatesMenuEnabled(true); this.menuBuilder.setNoUpdateAvailableMenuVisibility(true); this.menuBuilder.setUpdateAvailableMenuVisibility(false); @@ -61,17 +87,35 @@ export default class Updater { autoUpdater.on('update-cancelled', () => { logInfo('auto updater', 'Update cancelled'); - this.resetState(); }); autoUpdater.on('error', (err) => { logError('auto updater', 'Error checking for update', err); - this.resetState(); }); } + private async performInitialCheck() { + try { + logInfo('app updater', 'Checking for updates on application launch'); + await autoUpdater.checkForUpdatesAndNotify(); + } catch (e) { + logError('auto updater', 'Initial check failed', e as Error); + } + } + + private schedulePeriodicChecks() { + setInterval(async () => { + try { + logInfo('app updater', 'Checking for updates on a periodic schedule'); + await autoUpdater.checkForUpdatesAndNotify(); + } catch (e) { + logError('auto updater', 'Scheduled check failed', e as Error); + } + }, APPLICATION.UPDATE_CHECK_INTERVAL_MS); + } + private setTooltipWithStatus(status: string) { this.menubar.tray.setToolTip(`${APPLICATION.NAME}\n${status}`); } @@ -83,4 +127,19 @@ export default class Updater { this.menuBuilder.setUpdateAvailableMenuVisibility(false); this.menuBuilder.setUpdateReadyForInstallMenuVisibility(false); } + + private showUpdateReadyDialog(releaseName: string) { + const dialogOpts: MessageBoxOptions = { + type: 'info', + buttons: ['Restart', 'Later'], + title: 'Application Update', + message: `${APPLICATION.NAME} ${releaseName} has been downloaded`, + detail: + 'Restart to apply the update. You can also restart later from the tray menu.', + }; + + dialog.showMessageBox(dialogOpts).then((returnValue) => { + if (returnValue.response === 0) autoUpdater.quitAndInstall(); + }); + } } diff --git a/src/shared/constants.ts b/src/shared/constants.ts index f0133d7fe..84f8754c7 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -8,4 +8,6 @@ export const APPLICATION = { FIRST_RUN_FOLDER: 'gitify-first-run', WEBSITE: 'https://gitify.io/', + + UPDATE_CHECK_INTERVAL_MS: 24 * 60 * 60 * 1000, // 24 hours }; From c29fedb3983b6a1bcebea92708100c308c645241 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 27 Aug 2025 08:29:53 -0400 Subject: [PATCH 13/42] refactor: biome config imports (#2179) chore: update biome formatter config Signed-off-by: Adam Setch --- biome.json | 12 +- src/main/index.ts | 1 + src/main/menu.ts | 1 + src/main/updater.ts | 1 + src/renderer/App.tsx | 16 +-- src/renderer/components/AllRead.tsx | 2 +- src/renderer/components/Oops.tsx | 2 +- src/renderer/components/Sidebar.tsx | 103 +++++++++--------- .../components/avatars/AvatarWithFallback.tsx | 10 +- .../components/fields/Checkbox.test.tsx | 4 +- src/renderer/components/fields/Checkbox.tsx | 16 +-- src/renderer/components/fields/FieldLabel.tsx | 2 +- src/renderer/components/fields/RadioGroup.tsx | 18 +-- src/renderer/components/fields/Tooltip.tsx | 4 +- .../components/filters/FilterSection.test.tsx | 24 ++-- .../components/filters/FilterSection.tsx | 16 +-- .../components/filters/OrganizationFilter.tsx | 42 +++---- .../components/filters/ReasonFilter.tsx | 6 +- .../components/filters/StateFilter.tsx | 6 +- .../components/filters/SubjectTypeFilter.tsx | 6 +- .../components/filters/UserHandleFilter.tsx | 42 +++---- .../components/filters/UserTypeFilter.tsx | 6 +- src/renderer/components/icons/LogoIcon.tsx | 21 ++-- .../components/icons/VolumeDownIcon.tsx | 10 +- .../components/icons/VolumeUpIcon.tsx | 10 +- src/renderer/components/layout/Centered.tsx | 4 +- .../components/layout/EmojiSplash.tsx | 2 +- .../components/metrics/MetricGroup.tsx | 28 ++--- .../components/metrics/MetricPill.tsx | 8 +- .../notifications/AccountNotifications.tsx | 24 ++-- .../notifications/NotificationFooter.tsx | 12 +- .../notifications/NotificationHeader.tsx | 8 +- .../notifications/NotificationRow.tsx | 36 +++--- .../notifications/RepositoryNotifications.tsx | 30 ++--- .../components/primitives/EmojiText.tsx | 2 +- src/renderer/components/primitives/Header.tsx | 6 +- .../primitives/HoverButton.test.tsx | 10 +- .../components/primitives/HoverButton.tsx | 10 +- .../components/primitives/HoverGroup.tsx | 4 +- src/renderer/components/primitives/Title.tsx | 2 +- .../settings/AppearanceSettings.tsx | 52 ++++----- .../settings/NotificationSettings.tsx | 45 ++++---- .../components/settings/SettingsFooter.tsx | 15 +-- .../components/settings/SettingsReset.tsx | 12 +- .../components/settings/SystemSettings.tsx | 67 ++++++------ src/renderer/context/App.test.tsx | 18 +-- src/renderer/context/App.tsx | 1 + src/renderer/hooks/useNotifications.test.ts | 1 + src/renderer/hooks/useNotifications.ts | 1 + src/renderer/routes/Accounts.tsx | 65 +++++------ src/renderer/routes/Filters.tsx | 4 +- src/renderer/routes/Login.tsx | 13 ++- src/renderer/routes/LoginWithOAuthApp.tsx | 63 +++++------ .../routes/LoginWithPersonalAccessToken.tsx | 57 +++++----- src/renderer/routes/Notifications.tsx | 4 +- src/renderer/utils/api/client.test.ts | 1 + src/renderer/utils/api/client.ts | 1 + src/renderer/utils/api/request.ts | 1 + src/renderer/utils/auth/utils.ts | 1 + src/renderer/utils/comms.test.ts | 1 + src/renderer/utils/comms.ts | 1 + src/renderer/utils/helpers.test.ts | 1 + src/renderer/utils/helpers.ts | 1 + src/renderer/utils/notifications/native.ts | 1 + .../utils/notifications/notifications.test.ts | 1 + .../utils/notifications/notifications.ts | 1 + 66 files changed, 515 insertions(+), 481 deletions(-) diff --git a/biome.json b/biome.json index 4663b8ca1..6c5a29bc3 100644 --- a/biome.json +++ b/biome.json @@ -17,10 +17,13 @@ ":BLANK_LINE:", ":PACKAGE:", ":BLANK_LINE:", + "**/shared/**", + ":BLANK_LINE:", "**" ] } - } + }, + "useSortedAttributes": "on" } } }, @@ -41,7 +44,12 @@ "useExhaustiveDependencies": { "level": "warn", "options": { - "hooks": [{ "name": "useNavigate", "stableResult": true }] + "hooks": [ + { + "name": "useNavigate", + "stableResult": true + } + ] } }, "useUniqueElementIds": "warn" diff --git a/src/main/index.ts b/src/main/index.ts index f8f73d5a0..265b5f060 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -6,6 +6,7 @@ import { APPLICATION } from '../shared/constants'; import { namespacedEvent } from '../shared/events'; import { logInfo, logWarn } from '../shared/logger'; import { isLinux, isWindows } from '../shared/platform'; + import { onFirstRunMaybe } from './first-run'; import { TrayIcons } from './icons'; import MenuBuilder from './menu'; diff --git a/src/main/menu.ts b/src/main/menu.ts index 7465977e9..e4be9c4c6 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -4,6 +4,7 @@ import type { Menubar } from 'menubar'; import { APPLICATION } from '../shared/constants'; import { isMacOS, isWindows } from '../shared/platform'; + import { openLogsDirectory, resetApp, takeScreenshot } from './utils'; export default class MenuBuilder { diff --git a/src/main/updater.ts b/src/main/updater.ts index c1f5202a8..60dac989a 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -5,6 +5,7 @@ import type { Menubar } from 'menubar'; import { APPLICATION } from '../shared/constants'; import { logError, logInfo } from '../shared/logger'; + import type MenuBuilder from './menu'; /** diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 9f7f3694c..adf1f35b4 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -28,7 +28,7 @@ function RequireAuth({ children }) { return isLoggedIn ? ( children ) : ( - + ); } @@ -41,45 +41,45 @@ export const App = () => { } + path="/" /> } + path="/filters" /> } + path="/settings" /> } + path="/accounts" /> - } /> + } path="/login" /> } + path="/login-personal-access-token" /> } + path="/login-oauth-app" /> diff --git a/src/renderer/components/AllRead.tsx b/src/renderer/components/AllRead.tsx index 4109d4d33..5dca686a7 100644 --- a/src/renderer/components/AllRead.tsx +++ b/src/renderer/components/AllRead.tsx @@ -25,6 +25,6 @@ export const AllRead: FC = ({ fullHeight = true }: IAllRead) => { const heading = `No new ${hasFilters ? 'filtered ' : ''} notifications`; return ( - + ); }; diff --git a/src/renderer/components/Oops.tsx b/src/renderer/components/Oops.tsx index c6352b3da..38beccb1c 100644 --- a/src/renderer/components/Oops.tsx +++ b/src/renderer/components/Oops.tsx @@ -20,9 +20,9 @@ export const Oops: FC = ({ error, fullHeight = true }: IOops) => { return ( ); }; diff --git a/src/renderer/components/Sidebar.tsx b/src/renderer/components/Sidebar.tsx index 49c511401..c01c15c23 100644 --- a/src/renderer/components/Sidebar.tsx +++ b/src/renderer/components/Sidebar.tsx @@ -13,6 +13,7 @@ import { import { IconButton, Stack } from '@primer/react'; import { APPLICATION } from '../../shared/constants'; + import { AppContext } from '../context/App'; import { quitApp } from '../utils/comms'; import { Constants } from '../utils/constants'; @@ -72,129 +73,129 @@ export const Sidebar: FC = () => { return ( navigate('/', { replace: true })} size="small" - variant="invisible" tooltipDirection="e" - onClick={() => navigate('/', { replace: true })} - data-testid="sidebar-home" + unsafeDisableTooltip={false} + variant="invisible" /> 0 ? 'primary' : 'invisible'} - tooltipDirection="e" + icon={BellIcon} onClick={() => openGitHubNotifications(primaryAccountHostname)} - data-testid="sidebar-notifications" + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant={notificationsCount > 0 ? 'primary' : 'invisible'} /> {isLoggedIn && ( toggleFilters()} - data-testid="sidebar-filter-notifications" + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant={hasAnyFiltersSet(settings) ? 'primary' : 'invisible'} /> )} openGitHubIssues(primaryAccountHostname)} data-testid="sidebar-my-issues" + icon={IssueOpenedIcon} + onClick={() => openGitHubIssues(primaryAccountHostname)} + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> openGitHubPulls(primaryAccountHostname)} data-testid="sidebar-my-pull-requests" + icon={GitPullRequestIcon} + onClick={() => openGitHubPulls(primaryAccountHostname)} + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> {isLoggedIn && ( <> refreshNotifications()} - data-testid="sidebar-refresh" + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> toggleSettings()} data-testid="sidebar-settings" + icon={GearIcon} + onClick={() => toggleSettings()} + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> )} {!isLoggedIn && ( quitApp()} data-testid="sidebar-quit" + icon={XCircleIcon} + onClick={() => quitApp()} + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> )} diff --git a/src/renderer/components/avatars/AvatarWithFallback.tsx b/src/renderer/components/avatars/AvatarWithFallback.tsx index fc6607372..2f1116af1 100644 --- a/src/renderer/components/avatars/AvatarWithFallback.tsx +++ b/src/renderer/components/avatars/AvatarWithFallback.tsx @@ -31,24 +31,24 @@ export const AvatarWithFallback: React.FC = ({ // TODO explore using AnchoredOverlay component (https://primer.style/components/anchored-overlay/react/alpha) to render Avatar Card on hover return ( {!src || isBroken ? ( ) : ( setIsBroken(true)} size={size} square={isNonHuman} - onError={() => setIsBroken(true)} + src={src} /> )} {name && ( - + {name} )} diff --git a/src/renderer/components/fields/Checkbox.test.tsx b/src/renderer/components/fields/Checkbox.test.tsx index 623a59d91..7d004caf7 100644 --- a/src/renderer/components/fields/Checkbox.test.tsx +++ b/src/renderer/components/fields/Checkbox.test.tsx @@ -33,12 +33,12 @@ describe('renderer/components/fields/Checkbox.tsx', () => { }); it('should render - positive counter unselected', () => { - const tree = render(); + const tree = render(); expect(tree).toMatchSnapshot(); }); it('should render - positive counter selected', () => { - const tree = render(); + const tree = render(); expect(tree).toMatchSnapshot(); }); diff --git a/src/renderer/components/fields/Checkbox.tsx b/src/renderer/components/fields/Checkbox.tsx index 4a7e77c55..f78e54c32 100644 --- a/src/renderer/components/fields/Checkbox.tsx +++ b/src/renderer/components/fields/Checkbox.tsx @@ -26,27 +26,27 @@ export const Checkbox: FC = ({ return ( visible && ( diff --git a/src/renderer/components/fields/FieldLabel.tsx b/src/renderer/components/fields/FieldLabel.tsx index 633d1225d..471dc0b7f 100644 --- a/src/renderer/components/fields/FieldLabel.tsx +++ b/src/renderer/components/fields/FieldLabel.tsx @@ -7,7 +7,7 @@ export interface IFieldLabel { export const FieldLabel: FC = (props: IFieldLabel) => { return ( -