diff --git a/Sources/CodexBar/PreferencesGeneralPane.swift b/Sources/CodexBar/PreferencesGeneralPane.swift index e60992ce1..1856afe37 100644 --- a/Sources/CodexBar/PreferencesGeneralPane.swift +++ b/Sources/CodexBar/PreferencesGeneralPane.swift @@ -187,6 +187,10 @@ struct GeneralPane: View { .foregroundStyle(.secondary) } } + PreferenceToggleRow( + title: L("refresh_on_open_title"), + subtitle: L("refresh_on_open_subtitle"), + binding: self.$settings.refreshAllProvidersOnMenuOpen) PreferenceToggleRow( title: L("check_provider_status_title"), subtitle: L("check_provider_status_subtitle"), diff --git a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings index e4729e10e..188c1f57a 100644 --- a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "وتيرة التحديث"; "refresh_cadence_subtitle" = "كم مرة CodexBar استطلاعات في الخلفية."; "manual_refresh_hint" = "التحديث التلقائي مغلق؛ استخدم أمر التحديث في القائمة."; +"refresh_on_open_title" = "التحديث عند فتح القائمة"; +"refresh_on_open_subtitle" = "جلب أحدث بيانات الاستخدام لكل مزوّد في كل مرة تفتح فيها القائمة."; "check_provider_status_title" = "تحقق من حالة المزود"; "check_provider_status_subtitle" = "استطلاعات OpenAI/Claude صفحات الحالة و Google مساحة العمل Gemini/Antigravity، وتظهر الحوادث في الأيقونة والقائمة."; "session_quota_notifications_title" = "إشعارات حصص الجلسة"; diff --git a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings index 1ebe5878a..2cab7972c 100644 --- a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings @@ -420,6 +420,8 @@ "refresh_cadence_title" = "Freqüència d'actualització"; "refresh_cadence_subtitle" = "Amb quina freqüència el CodexBar consulta els proveïdors en segon pla."; "manual_refresh_hint" = "L'actualització automàtica està desactivada; feu servir l'ordre Actualitza del menú."; +"refresh_on_open_title" = "Actualitza en obrir el menú"; +"refresh_on_open_subtitle" = "Obté l'ús més recent de cada proveïdor cada vegada que obres el menú."; "check_provider_status_title" = "Comproveu l'estat del proveïdor"; "check_provider_status_subtitle" = "Consulta les pàgines d'estat d'OpenAI/Claude i Google Workspace per a Gemini/Antigravity, mostrant incidències a la icona i al menú."; "session_quota_notifications_title" = "Notificacions de quota de sessió"; diff --git a/Sources/CodexBar/Resources/de.lproj/Localizable.strings b/Sources/CodexBar/Resources/de.lproj/Localizable.strings index cba114925..71e4039d2 100644 --- a/Sources/CodexBar/Resources/de.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/de.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "Aktualisierungsintervall"; "refresh_cadence_subtitle" = "Wie oft CodexBar Anbieter im Hintergrund abfragt."; "manual_refresh_hint" = "Auto-Aktualisierung ist aus; nutze im Menü den Befehl „Aktualisieren“."; +"refresh_on_open_title" = "Beim Öffnen des Menüs aktualisieren"; +"refresh_on_open_subtitle" = "Bei jedem Öffnen des Menüs die aktuelle Nutzung aller Anbieter abrufen."; "check_provider_status_title" = "Anbieterstatus prüfen"; "check_provider_status_subtitle" = "Prüft OpenAI/Claude-Statusseiten und Google Workspace für Gemini/Antigravity und zeigt Vorfälle in Icon und Menü."; "session_quota_notifications_title" = "Sitzungs-Quota-Benachrichtigungen"; diff --git a/Sources/CodexBar/Resources/en.lproj/Localizable.strings b/Sources/CodexBar/Resources/en.lproj/Localizable.strings index 50db29fba..97381454f 100644 --- a/Sources/CodexBar/Resources/en.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/en.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "Refresh cadence"; "refresh_cadence_subtitle" = "How often CodexBar polls providers in the background."; "manual_refresh_hint" = "Auto-refresh is off; use the menu's Refresh command."; +"refresh_on_open_title" = "Refresh when the menu opens"; +"refresh_on_open_subtitle" = "Fetch the latest usage for every provider each time you open the menu."; "check_provider_status_title" = "Check provider status"; "check_provider_status_subtitle" = "Polls OpenAI/Claude status pages and Google Workspace for Gemini/Antigravity, surfacing incidents in the icon and menu."; "session_quota_notifications_title" = "Session quota notifications"; diff --git a/Sources/CodexBar/Resources/es.lproj/Localizable.strings b/Sources/CodexBar/Resources/es.lproj/Localizable.strings index a48add1d0..703c10fc0 100644 --- a/Sources/CodexBar/Resources/es.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/es.lproj/Localizable.strings @@ -420,6 +420,8 @@ "refresh_cadence_title" = "Frecuencia de actualización"; "refresh_cadence_subtitle" = "Con qué frecuencia CodexBar consulta a los proveedores en segundo plano."; "manual_refresh_hint" = "La actualización automática está desactivada; usa el comando Actualizar del menú."; +"refresh_on_open_title" = "Actualizar al abrir el menú"; +"refresh_on_open_subtitle" = "Obtén el uso más reciente de cada proveedor cada vez que abres el menú."; "check_provider_status_title" = "Comprobar estado del proveedor"; "check_provider_status_subtitle" = "Consulta las páginas de estado de OpenAI/Claude y Google Workspace para Gemini/Antigravity, mostrando incidencias en el icono y el menú."; "session_quota_notifications_title" = "Notificaciones de cuota de sesión"; diff --git a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings index 83a08a91d..7be017bc8 100644 --- a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "کادانس تازه سازی"; "refresh_cadence_subtitle" = "چند وقت یکبار CodexBar ارائه دهندگان نظرسنجی در پس زمینه انجام می دهند."; "manual_refresh_hint" = "تازه سازی خودکار خاموش است؛ از فرمان تازه سازی منو استفاده کنید."; +"refresh_on_open_title" = "بازخوانی هنگام باز کردن منو"; +"refresh_on_open_subtitle" = "هر بار که منو را باز می‌کنید، آخرین میزان مصرف هر ارائه‌دهنده دریافت می‌شود."; "check_provider_status_title" = "وضعیت ارائه دهنده را بررسی کنید"; "check_provider_status_subtitle" = "نظرسنجی ها OpenAI/Claude صفحات وضعیت و Google Workspace برای Gemini/Antigravity که حوادث را در آیکون و منو نمایش می دهد."; "session_quota_notifications_title" = "اعلان های سهمیه نشست"; diff --git a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings index ad47dc984..8b68ef252 100644 --- a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "Fréquence d'actualisation"; "refresh_cadence_subtitle" = "Définit la fréquence à laquelle CodexBar interroge les fournisseurs en arrière-plan."; "manual_refresh_hint" = "L'actualisation automatique est désactivée ; utilisez la commande Actualiser du menu."; +"refresh_on_open_title" = "Actualiser à l'ouverture du menu"; +"refresh_on_open_subtitle" = "Récupère l'utilisation la plus récente de chaque fournisseur à chaque ouverture du menu."; "check_provider_status_title" = "Vérifier l'état des fournisseurs"; "check_provider_status_subtitle" = "Interroge les pages d'état OpenAI/Claude et Google Workspace pour Gemini/Antigravity, et affiche les incidents dans l'icône et le menu."; "session_quota_notifications_title" = "Notifications de quota de session"; diff --git a/Sources/CodexBar/Resources/id.lproj/Localizable.strings b/Sources/CodexBar/Resources/id.lproj/Localizable.strings index a37fe3893..c2a5eb70f 100644 --- a/Sources/CodexBar/Resources/id.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/id.lproj/Localizable.strings @@ -424,6 +424,8 @@ "refresh_cadence_title" = "Frekuensi penyegaran"; "refresh_cadence_subtitle" = "Seberapa sering CodexBar memeriksa penyedia di latar belakang."; "manual_refresh_hint" = "Penyegaran otomatis nonaktif; gunakan perintah Segarkan di menu."; +"refresh_on_open_title" = "Segarkan saat menu dibuka"; +"refresh_on_open_subtitle" = "Ambil penggunaan terbaru untuk setiap penyedia setiap kali Anda membuka menu."; "check_provider_status_title" = "Periksa status penyedia"; "check_provider_status_subtitle" = "Memeriksa halaman status OpenAI/Claude dan Google Workspace untuk Gemini/Antigravity, menampilkan insiden di ikon dan menu."; "session_quota_notifications_title" = "Notifikasi kuota sesi"; diff --git a/Sources/CodexBar/Resources/it.lproj/Localizable.strings b/Sources/CodexBar/Resources/it.lproj/Localizable.strings index ea446916a..68feb1dad 100644 --- a/Sources/CodexBar/Resources/it.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/it.lproj/Localizable.strings @@ -424,6 +424,8 @@ "refresh_cadence_title" = "Frequenza aggiornamento"; "refresh_cadence_subtitle" = "Con quale frequenza CodexBar interroga i provider in background."; "manual_refresh_hint" = "Aggiornamento automatico disattivato; usa il comando Aggiorna nel menu."; +"refresh_on_open_title" = "Aggiorna all'apertura del menu"; +"refresh_on_open_subtitle" = "Recupera l'utilizzo più recente di ogni provider ogni volta che apri il menu."; "check_provider_status_title" = "Controlla stato provider"; "check_provider_status_subtitle" = "Controlla le pagine di stato OpenAI/Claude e Google Workspace per Gemini/Antigravity, mostrando incidenti in icona e menu."; "session_quota_notifications_title" = "Notifiche quota sessione"; diff --git a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings index 2ba6ce470..5d6be2c3a 100644 --- a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings @@ -419,6 +419,8 @@ "refresh_cadence_title" = "更新間隔"; "refresh_cadence_subtitle" = "CodexBar がバックグラウンドでプロバイダをポーリングする頻度です。"; "manual_refresh_hint" = "自動更新はオフです。メニューの「更新」コマンドを使用してください。"; +"refresh_on_open_title" = "メニューを開いたときに更新"; +"refresh_on_open_subtitle" = "メニューを開くたびに、すべてのプロバイダーの最新の使用状況を取得します。"; "check_provider_status_title" = "プロバイダのステータスを確認"; "check_provider_status_subtitle" = "OpenAI/Claude のステータスページと Gemini/Antigravity 用の Google Workspace をポーリングし、障害情報をアイコンとメニューに表示します。"; "session_quota_notifications_title" = "セッションクォータ通知"; diff --git a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings index 47a473f90..8bde610cc 100644 --- a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings @@ -419,6 +419,8 @@ "refresh_cadence_title" = "새로 고침 주기"; "refresh_cadence_subtitle" = "CodexBar가 백그라운드에서 공급자를 폴링하는 빈도입니다."; "manual_refresh_hint" = "자동 새로 고침이 꺼져 있습니다. 메뉴의 새로 고침 명령을 사용하세요."; +"refresh_on_open_title" = "메뉴를 열 때 새로 고침"; +"refresh_on_open_subtitle" = "메뉴를 열 때마다 모든 공급자의 최신 사용량을 가져옵니다."; "check_provider_status_title" = "공급자 상태 확인"; "check_provider_status_subtitle" = "OpenAI/Claude 상태 페이지와 Gemini/Antigravity용 Google Workspace를 폴링하여 아이콘과 메뉴에 문제를 표시합니다."; "session_quota_notifications_title" = "세션 할당량 알림"; diff --git a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings index d811b403b..572a635d0 100644 --- a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "Cadans vernieuwen"; "refresh_cadence_subtitle" = "Hoe vaak CodexBar providers op de achtergrond ondervraagt."; "manual_refresh_hint" = "Automatisch vernieuwen is uitgeschakeld; gebruik de opdracht Vernieuwen van het menu."; +"refresh_on_open_title" = "Vernieuwen bij openen van menu"; +"refresh_on_open_subtitle" = "Haalt bij elke keer dat je het menu opent het meest recente gebruik van elke provider op."; "check_provider_status_title" = "Controleer de status van de provider"; "check_provider_status_subtitle" = "Polls van OpenAI/Claude-statuspagina's en Google Workspace voor Gemini/Antigravity, waarbij incidenten in het pictogram en het menu worden weergegeven."; "session_quota_notifications_title" = "Meldingen over sessiequota"; diff --git a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings index d4a2463cc..be15b121f 100644 --- a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings @@ -424,6 +424,8 @@ "refresh_cadence_title" = "Częstotliwość odświeżania"; "refresh_cadence_subtitle" = "Jak często CodexBar odpyta dostawców w tle."; "manual_refresh_hint" = "Auto-odświeżanie jest wyłączone; użyj polecenia Odśwież w menu."; +"refresh_on_open_title" = "Odśwież po otwarciu menu"; +"refresh_on_open_subtitle" = "Pobiera najnowsze zużycie każdego dostawcy przy każdym otwarciu menu."; "check_provider_status_title" = "Sprawdzaj status dostawców"; "check_provider_status_subtitle" = "Sprawdza status OpenAI/Claude oraz Google Workspace dla Gemini/Antigravity i pokazuje incydenty na ikonie i w menu."; "session_quota_notifications_title" = "Powiadomienia o limicie sesji"; diff --git a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings index a0f431795..7242a7642 100644 --- a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings @@ -419,6 +419,8 @@ "refresh_cadence_title" = "Cadência de atualização"; "refresh_cadence_subtitle" = "Frequência com que o CodexBar consulta provedores em segundo plano."; "manual_refresh_hint" = "A atualização automática está desativada; use Atualizar no menu."; +"refresh_on_open_title" = "Atualizar ao abrir o menu"; +"refresh_on_open_subtitle" = "Busca o uso mais recente de cada provedor sempre que você abre o menu."; "check_provider_status_title" = "Verificar status dos provedores"; "check_provider_status_subtitle" = "Consulta páginas de status da OpenAI/Claude e o Google Workspace para Gemini/Antigravity, exibindo incidentes no ícone e no menu."; "session_quota_notifications_title" = "Notificações de cota de sessão"; diff --git a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings index 9ecfc8e8d..7c4fcc8c2 100644 --- a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "Uppdateringsintervall"; "refresh_cadence_subtitle" = "Hur ofta CodexBar kontrollerar leverantörer i bakgrunden."; "manual_refresh_hint" = "Automatisk uppdatering är avstängd. Använd Uppdatera i menyn."; +"refresh_on_open_title" = "Uppdatera när menyn öppnas"; +"refresh_on_open_subtitle" = "Hämtar den senaste användningen för varje leverantör varje gång du öppnar menyn."; "check_provider_status_title" = "Kontrollera leverantörsstatus"; "check_provider_status_subtitle" = "Kontrollerar OpenAI/Claude-statussidor och Google Workspace för Gemini/Antigravity och visar incidenter i ikonen och menyn."; "session_quota_notifications_title" = "Aviseringar för sessionskvot"; diff --git a/Sources/CodexBar/Resources/th.lproj/Localizable.strings b/Sources/CodexBar/Resources/th.lproj/Localizable.strings index 93057705c..43667616d 100644 --- a/Sources/CodexBar/Resources/th.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/th.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "จังหวะการรีเฟรช"; "refresh_cadence_subtitle" = "ความถี่ในการ CodexBar ผู้ให้บริการโพลในเบื้องหลัง"; "manual_refresh_hint" = "การรีเฟรชอัตโนมัติปิดอยู่ ใช้คําสั่งรีเฟรชของเมนู"; +"refresh_on_open_title" = "รีเฟรชเมื่อเปิดเมนู"; +"refresh_on_open_subtitle" = "ดึงข้อมูลการใช้งานล่าสุดของผู้ให้บริการทุกรายทุกครั้งที่คุณเปิดเมนู"; "check_provider_status_title" = "ตรวจสอบสถานะผู้ให้บริการ"; "check_provider_status_subtitle" = "โพล OpenAI/Claude หน้าสถานะและ Google Workspace for Gemini/Antigravity โดยแสดงเหตุการณ์ในไอคอนและเมนู"; "session_quota_notifications_title" = "การแจ้งเตือนโควต้าเซสชัน"; diff --git a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings index dfcff2346..3f08dc47b 100644 --- a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "Yenileme sıklığı"; "refresh_cadence_subtitle" = "CodexBar'ın arka planda sağlayıcıları ne sıklıkla sorgulayacağı."; "manual_refresh_hint" = "Otomatik yenileme kapalı; menüdeki Yenile komutunu kullanın."; +"refresh_on_open_title" = "Menü açıldığında yenile"; +"refresh_on_open_subtitle" = "Menüyü her açtığınızda her sağlayıcının en güncel kullanımını getirir."; "check_provider_status_title" = "Sağlayıcı durumunu denetle"; "check_provider_status_subtitle" = "OpenAI/Claude durum sayfalarını ve Google Workspace'i (Gemini/Antigravity) sorgular, olayları simge ve menüde gösterir."; "session_quota_notifications_title" = "Oturum kota bildirimleri"; diff --git a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings index ee73c7afd..12e8447b3 100644 --- a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings @@ -422,6 +422,8 @@ "refresh_cadence_title" = "Оновити каденцію"; "refresh_cadence_subtitle" = "Як часто CodexBar опитує постачальників у фоновому режимі."; "manual_refresh_hint" = "Автооновлення вимкнено; скористайтеся командою меню «Оновити»."; +"refresh_on_open_title" = "Оновлювати при відкритті меню"; +"refresh_on_open_subtitle" = "Отримує найновіші дані про використання для кожного провайдера щоразу, коли ви відкриваєте меню."; "check_provider_status_title" = "Перевірте статус провайдера"; "check_provider_status_subtitle" = "Опитує сторінки статусу OpenAI/Claude і Google Workspace для Gemini/Antigravity, виявляючи інциденти в значку та меню."; "session_quota_notifications_title" = "Сповіщення про квоту сеансу"; diff --git a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings index 7438c3ec4..04597639d 100644 --- a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings @@ -418,6 +418,8 @@ "refresh_cadence_title" = "Nhịp làm mới"; "refresh_cadence_subtitle" = "Tần suất CodexBar thăm dò ý kiến ​​các nhà cung cấp trong nền."; "manual_refresh_hint" = "Tính năng tự động làm mới bị tắt; sử dụng lệnh Làm mới của menu."; +"refresh_on_open_title" = "Làm mới khi mở menu"; +"refresh_on_open_subtitle" = "Tải mức sử dụng mới nhất của mọi nhà cung cấp mỗi khi bạn mở menu."; "check_provider_status_title" = "Kiểm tra Nhà cung cấp trạng thái"; "check_provider_status_subtitle" = "Thăm dò ý kiến ​​OpenAI / Claude các trang trạng thái và Google Không gian làm việc dành cho Gemini /AntiGravity, phát hiện các sự cố trong biểu tượng và menu."; "session_quota_notifications_title" = "Thông báo về phiên Hạn mức"; diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index ed10dfb73..0f8dd6ba9 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -426,6 +426,8 @@ "refresh_cadence_title" = "刷新频率"; "refresh_cadence_subtitle" = "CodexBar 在后台轮询提供商的频率。"; "manual_refresh_hint" = "自动刷新已关闭;请使用菜单中的“刷新”命令。"; +"refresh_on_open_title" = "打开菜单时刷新"; +"refresh_on_open_subtitle" = "每次打开菜单时获取每个提供商的最新用量。"; "check_provider_status_title" = "检查提供商状态"; "check_provider_status_subtitle" = "轮询 OpenAI/Claude 状态页面和 Google Workspace 的 Gemini/Antigravity,在图标和菜单中显示故障信息。"; "session_quota_notifications_title" = "会话配额通知"; diff --git a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings index ebde59b02..e95bc3de8 100644 --- a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings @@ -426,6 +426,8 @@ "refresh_cadence_title" = "重新整理頻率"; "refresh_cadence_subtitle" = "CodexBar 在背景輪詢提供者的頻率。"; "manual_refresh_hint" = "自動重新整理已關閉;請使用選單中的「重新整理」指令。"; +"refresh_on_open_title" = "開啟選單時重新整理"; +"refresh_on_open_subtitle" = "每次開啟選單時擷取每個供應商的最新用量。"; "check_provider_status_title" = "檢查提供者狀態"; "check_provider_status_subtitle" = "輪詢 OpenAI/Claude 狀態頁面和 Google Workspace 的 Gemini/Antigravity,並在圖示和選單中顯示服務異常資訊。"; "session_quota_notifications_title" = "工作階段配額通知"; diff --git a/Sources/CodexBar/SettingsStore+Defaults.swift b/Sources/CodexBar/SettingsStore+Defaults.swift index c10b5592e..b15b4ced0 100644 --- a/Sources/CodexBar/SettingsStore+Defaults.swift +++ b/Sources/CodexBar/SettingsStore+Defaults.swift @@ -13,6 +13,16 @@ extension SettingsStore { } } + /// When enabled, opening the menu refreshes usage for every enabled provider (on top of the + /// periodic refresh clock, which is left untouched). See `scheduleOpenMenuRefresh`. + var refreshAllProvidersOnMenuOpen: Bool { + get { self.defaultsState.refreshAllProvidersOnMenuOpen } + set { + self.defaultsState.refreshAllProvidersOnMenuOpen = newValue + self.userDefaults.set(newValue, forKey: "refreshAllProvidersOnMenuOpen") + } + } + var launchAtLogin: Bool { get { self.defaultsState.launchAtLogin } set { diff --git a/Sources/CodexBar/SettingsStore.swift b/Sources/CodexBar/SettingsStore.swift index e9a087ca1..7d16f91c9 100644 --- a/Sources/CodexBar/SettingsStore.swift +++ b/Sources/CodexBar/SettingsStore.swift @@ -335,6 +335,8 @@ extension SettingsStore { if Self.isRunningTests, refreshDefault == nil { userDefaults.set(refreshFrequency.rawValue, forKey: "refreshFrequency") } + let refreshAllProvidersOnMenuOpen = userDefaults.object( + forKey: "refreshAllProvidersOnMenuOpen") as? Bool ?? false let launchAtLogin = userDefaults.object(forKey: "launchAtLogin") as? Bool ?? false let debugMenuEnabled = userDefaults.object(forKey: "debugMenuEnabled") as? Bool ?? false let debugDisableKeychainAccess = Self.loadDebugDisableKeychainAccess(userDefaults: userDefaults) @@ -419,6 +421,7 @@ extension SettingsStore { let appLanguageRaw = userDefaults.string(forKey: "appLanguage") return SettingsDefaultsState( refreshFrequency: refreshFrequency, + refreshAllProvidersOnMenuOpen: refreshAllProvidersOnMenuOpen, launchAtLogin: launchAtLogin, debugMenuEnabled: debugMenuEnabled, debugDisableKeychainAccess: debugDisableKeychainAccess, diff --git a/Sources/CodexBar/SettingsStoreState.swift b/Sources/CodexBar/SettingsStoreState.swift index 58f7a02af..a3d0f9bce 100644 --- a/Sources/CodexBar/SettingsStoreState.swift +++ b/Sources/CodexBar/SettingsStoreState.swift @@ -2,6 +2,7 @@ import Foundation struct SettingsDefaultsState { var refreshFrequency: RefreshFrequency + var refreshAllProvidersOnMenuOpen: Bool var launchAtLogin: Bool var debugMenuEnabled: Bool var debugDisableKeychainAccess: Bool diff --git a/Sources/CodexBar/StatusItemController+Menu.swift b/Sources/CodexBar/StatusItemController+Menu.swift index 96baea2e1..daeee94e6 100644 --- a/Sources/CodexBar/StatusItemController+Menu.swift +++ b/Sources/CodexBar/StatusItemController+Menu.swift @@ -1126,6 +1126,9 @@ extension StatusItemController { // provider fetch failed and needs a retry; periodic freshness is handled by the refresh timer. // AppKit menu tracking is modal, so starting provider refreshes while it is active can make the menu // feel frozen and can block keyboard focus from returning. + // Exception: when `refreshAllProvidersOnMenuOpen` is enabled, every enabled provider is refreshed on + // open regardless of freshness — still after the delay below, and still via the light usage-only + // primitive so the OpenAI dashboard scrape stays deferred until the menu closes. let providersNeedingRetryAtOpen = self.delayedRefreshRetryProviders(for: menu).filter { self.store.isStale(provider: $0) || self.store.snapshot(for: $0) == nil } @@ -1143,10 +1146,18 @@ extension StatusItemController { self.onDelayedMenuRefreshAttemptForTesting?() #endif guard self.openMenus[ObjectIdentifier(menu)] != nil else { return } + let refreshAllOnOpen = self.settings.refreshAllProvidersOnMenuOpen let availableProviders = Set(self.store.enabledProvidersForBackgroundWork()) - let retryProviders = self.delayedRefreshRetryProviders(for: menu).filter { + // When "refresh all providers on open" is enabled, consider every enabled provider and refresh + // it regardless of freshness; otherwise keep the conservative stale/missing retry set scoped to + // the providers actually rendered in this menu. + let candidateProviders = refreshAllOnOpen + ? self.store.enabledProvidersForBackgroundWork() + : self.delayedRefreshRetryProviders(for: menu) + let retryProviders = candidateProviders.filter { availableProviders.contains($0) && - (self.store.refreshingProviders.contains($0) || + (refreshAllOnOpen || + self.store.refreshingProviders.contains($0) || self.store.isStale(provider: $0) || self.store.snapshot(for: $0) == nil) } @@ -1165,9 +1176,22 @@ extension StatusItemController { } self.deferMenuInteractionRefreshIfNeeded(providers: retryProviders) await ProviderInteractionContext.$current.withValue(.background) { - for provider in retryProviders { - guard !Task.isCancelled else { return } - await self.store.refreshProvider(provider, coalesceIfRefreshing: true) + if refreshAllOnOpen { + // Refresh concurrently so one slow provider doesn't delay the rest, mirroring the + // periodic refresh in `UsageStore.runRefresh`. `coalesceIfRefreshing` makes each call + // wait for any in-flight refresh (e.g. a manual refresh) instead of overriding it. + await withTaskGroup(of: Void.self) { group in + for provider in retryProviders { + group.addTask { + await self.store.refreshProvider(provider, coalesceIfRefreshing: true) + } + } + } + } else { + for provider in retryProviders { + guard !Task.isCancelled else { return } + await self.store.refreshProvider(provider, coalesceIfRefreshing: true) + } } } let stillNeedsRetry = retryProviders.contains { diff --git a/Tests/CodexBarTests/StatusMenuInstantOpenTests.swift b/Tests/CodexBarTests/StatusMenuInstantOpenTests.swift index cc308f0e2..217d568a7 100644 --- a/Tests/CodexBarTests/StatusMenuInstantOpenTests.swift +++ b/Tests/CodexBarTests/StatusMenuInstantOpenTests.swift @@ -57,6 +57,122 @@ extension StatusMenuTests { #expect(refreshInteractions.isEmpty) } + @Test + func `menu open refreshes fresh providers when refresh-all-on-open is enabled`() async { + self.disableMenuCardsForTesting() + let settings = self.makeSettings() + settings.statusChecksEnabled = false + settings.refreshFrequency = .manual + settings.mergeIcons = false + settings.refreshAllProvidersOnMenuOpen = true + // Enable several providers; only the available ones land in background work. The + // assertion below compares against that resolved set, so it stays robust regardless. + self.enableProvidersForInstantOpenTesting([.codex, .claude, .factory], settings: settings) + + let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false) + // Give every enabled provider a fresh, non-stale snapshot so the ONLY reason to refresh on + // open is the new setting — not a stale/missing retry (which is the pre-existing behavior). + let now = Date() + for provider in store.enabledProvidersForBackgroundWork() { + store._setSnapshotForTesting( + UsageSnapshot( + primary: RateWindow( + usedPercent: 20, + windowMinutes: 300, + resetsAt: now.addingTimeInterval(1800), + resetDescription: nil), + secondary: nil, + updatedAt: now), + provider: provider) + } + var refreshedProviders: Set = [] + var refreshInteractions: [ProviderInteraction] = [] + store._test_providerRefreshOverride = { provider in + refreshedProviders.insert(provider) + refreshInteractions.append(ProviderInteractionContext.current) + } + defer { store._test_providerRefreshOverride = nil } + + let controller = StatusItemController( + store: store, + settings: settings, + account: UsageFetcher().loadAccountInfo(), + updater: DisabledUpdaterController(), + preferencesSelection: PreferencesSelection(), + statusBar: self.makeStatusBarForTesting()) + defer { controller.releaseStatusItemsForTesting() } + StatusItemController.setClosedMenuPreparationDelayForTesting(.zero) + defer { StatusItemController.resetClosedMenuPreparationDelayForTesting() } + + controller.menuRefreshEnabledOverrideForTesting = true + StatusItemController.setDeferredMenuInteractionRefreshDelayForTesting(.zero) + defer { StatusItemController.resetDeferredMenuInteractionRefreshDelayForTesting() } + StatusItemController.setMenuOpenRefreshDelayForTesting(.zero) + defer { StatusItemController.resetMenuOpenRefreshDelayForTesting() } + + let expectedProviders = Set(store.enabledProvidersForBackgroundWork()) + let menu = controller.makeMenu() + controller.menuWillOpen(menu) + defer { controller.menuDidClose(menu) } + + for _ in 0..<80 where refreshedProviders != expectedProviders { + await Task.yield() + } + + // Every enabled provider is refreshed on open even though all snapshots were fresh. + #expect(refreshedProviders == expectedProviders) + #expect(expectedProviders.contains(.codex)) + #expect(!refreshInteractions.isEmpty) + #expect(refreshInteractions.allSatisfy { $0 == .background }) + } + + @Test + func `menu open leaves fresh provider untouched when refresh-all-on-open is disabled`() async { + self.disableMenuCardsForTesting() + let settings = self.makeSettings() + settings.statusChecksEnabled = false + settings.refreshFrequency = .manual + settings.mergeIcons = false + settings.refreshAllProvidersOnMenuOpen = false + self.enableOnlyCodexForInstantOpenTesting(settings) + + let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false) + var providerRefreshCount = 0 + store._test_providerRefreshOverride = { _ in + providerRefreshCount += 1 + } + defer { store._test_providerRefreshOverride = nil } + + let controller = StatusItemController( + store: store, + settings: settings, + account: UsageFetcher().loadAccountInfo(), + updater: DisabledUpdaterController(), + preferencesSelection: PreferencesSelection(), + statusBar: self.makeStatusBarForTesting()) + defer { controller.releaseStatusItemsForTesting() } + StatusItemController.setClosedMenuPreparationDelayForTesting(.zero) + defer { StatusItemController.resetClosedMenuPreparationDelayForTesting() } + + controller.menuRefreshEnabledOverrideForTesting = true + StatusItemController.setDeferredMenuInteractionRefreshDelayForTesting(.zero) + defer { StatusItemController.resetDeferredMenuInteractionRefreshDelayForTesting() } + // Let the delayed open-refresh task actually fire; with the setting off and fresh data, + // it must still skip the refresh (today's stale/missing-only behavior). + StatusItemController.setMenuOpenRefreshDelayForTesting(.zero) + defer { StatusItemController.resetMenuOpenRefreshDelayForTesting() } + + let menu = controller.makeMenu() + controller.menuWillOpen(menu) + defer { controller.menuDidClose(menu) } + + for _ in 0..<40 { + await Task.yield() + } + + #expect(providerRefreshCount == 0) + } + @Test func `delayed open refresh does not rebuild fresh menu after unrelated data invalidation`() async { self.disableMenuCardsForTesting()