Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import co.touchlab.kermit.Logger
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import zed.rainxch.core.domain.model.WhatsNewEntry
Expand All @@ -30,21 +32,41 @@ class WhatsNewViewModel(
val hasHistory: StateFlow<Boolean> = _hasHistory.asStateFlow()

init {
// Re-load whenever the user's selected app language changes.
// The loader resolves locale per call, but cached state on this
// VM would otherwise serve the previous language's text after
// the user switches in Tweaks. distinctUntilChanged guards
// against the initial replay-emit firing the load twice.
viewModelScope.launch {
try {
evaluate()
tweaksRepository
.getAppLanguage()
.distinctUntilChanged()
.collect {
reloadHistory()
reloadPending()
}
} catch (t: Throwable) {
logger.e(t) { "Failed to evaluate what's-new state" }
logger.e(t) { "Failed to observe app-language for what's-new reloads" }
Comment on lines 49 to +50
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Kotlin coroutines, should CancellationException be rethrown when using catch(Throwable) in ViewModel coroutine blocks?

💡 Result:

Yes—when you catch a broad Throwable in code running in viewModelScope (or any structured coroutine), you should not “consume” CancellationException. Either (a) don’t catch it in the first place by catching specific exceptions, or (b) if you must catch Throwable, then rethrow when it’s CancellationException (directly or via ensureActive()), so coroutine cancellation can complete correctly. Rationale (grounded in docs): 1) Android’s coroutines best-practices explicitly says: “don’t consume exceptions of type CancellationException (don’t catch them, or always rethrow them if caught)” [1]. 2) Kotlin coroutines treat cancellation as exceptional control flow: coroutines use CancellationException for cancellation, and “these exceptions are ignored by all handlers” [2]. If you catch them and don’t rethrow/propagate, you prevent the coroutine from terminating as cancellation expects. 3) Kotlin documentation recommends using a try/catch to handle exceptions within the coroutine body, and notes that CancellationException is part of the cancellation mechanism (and is ignored by handlers) [3][2]. Practical pattern when using catch(Throwable): - Prefer catching specific exceptions (e.g., IOException) instead of Throwable in viewModelScope blocks. - If you must catch Throwable, do: - if e is CancellationException -> rethrow e - otherwise handle/log the error A common equivalent is coroutineContext.ensureActive() at the top of the catch, which will rethrow cancellation if the coroutine is actually cancelled. So the direct answer to your question is: yes, CancellationException should effectively be rethrown (not swallowed) when using catch(Throwable) in ViewModel coroutine blocks, to preserve correct cooperative cancellation [1][2].

Citations:


🏁 Script executed:

# Check if the file exists and read the relevant sections
fd -t f "WhatsNewViewModel.kt" composeApp/src/commonMain/kotlin/

# Get file size
wc -l composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt

# Read the file around the specified lines (40-75 to see context)
sed -n '40,75p' composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt | cat -n

Repository: OpenHub-Store/GitHub-Store

Length of output: 1655


🏁 Script executed:

# Read the beginning of the file to check package name and imports
head -40 composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt | cat -n

Repository: OpenHub-Store/GitHub-Store

Length of output: 2109


🏁 Script executed:

# Verify the actual line numbers - show lines 49-50, 60-61, 68-69 with full context
sed -n '45,75p' composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt | cat -n

Repository: OpenHub-Store/GitHub-Store

Length of output: 1269


Rethrow CancellationException in broad Throwable catches to preserve cancellation semantics.

Lines 49-50, 60-61, and 68-69 use catch (t: Throwable), which swallows CancellationException. This breaks ViewModel cancellation and can emit misleading error logs when the ViewModel is cleared. Add if (t is CancellationException) throw t in each block and import CancellationException from kotlinx.coroutines.

Proposed fix
+import kotlinx.coroutines.CancellationException
@@
-            } catch (t: Throwable) {
+            } catch (t: Throwable) {
+                if (t is CancellationException) throw t
                 logger.e(t) { "Failed to observe app-language for what's-new reloads" }
             }
@@
-        } catch (t: Throwable) {
+        } catch (t: Throwable) {
+            if (t is CancellationException) throw t
             logger.e(t) { "Failed to load what's-new history" }
         }
@@
-        } catch (t: Throwable) {
+        } catch (t: Throwable) {
+            if (t is CancellationException) throw t
             logger.e(t) { "Failed to evaluate what's-new state" }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt`
around lines 49 - 50, In the catch blocks that currently use "catch (t:
Throwable)" (the ones that log via logger.e(t) { "Failed to observe app-language
for what's-new reloads" } and the other two similar catches inside
WhatsNewViewModel), check for CancellationException and rethrow it before
logging/handling: add "if (t is CancellationException) throw t" at the top of
each catch, and add the import kotlinx.coroutines.CancellationException; this
preserves coroutine cancellation semantics while keeping the existing error
logging for non-cancellation failures.

}
}
viewModelScope.launch {
try {
val entries = whatsNewLoader.loadAll()
_historyEntries.value = entries
_hasHistory.value = entries.size > 1
} catch (t: Throwable) {
logger.e(t) { "Failed to load what's-new history" }
}
}

private suspend fun reloadHistory() {
try {
val entries = whatsNewLoader.loadAll()
_historyEntries.value = entries
_hasHistory.value = entries.size > 1
} catch (t: Throwable) {
logger.e(t) { "Failed to load what's-new history" }
}
}

private suspend fun reloadPending() {
try {
evaluate()
} catch (t: Throwable) {
logger.e(t) { "Failed to evaluate what's-new state" }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"Liquid Glass effect removed — icons in dark mode could become invisible against transparent backgrounds. Cleaner, higher-contrast UI everywhere.",
"Store no longer claims an update is available for itself after you've already updated it.",
"Apps screen — Add-by-link button no longer covers the last app in the list.",
"Search no longer triggers the GitHub rate-limit dialog when the backend itself is busy — surfaces a friendly retry-after toast instead."
"Search no longer triggers the GitHub rate-limit dialog when the backend itself is busy — surfaces a friendly retry-after toast instead.",
"What's-new sheet and history now switch to the newly-selected language without needing an app restart."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"تمت إزالة تأثير الزجاج السائل — قد تختفي الأيقونات في الوضع الداكن على الخلفيات الشفافة. واجهة أنظف بتباين أعلى في كل مكان.",
"المتجر لم يعد يدّعي توفر تحديث لنفسه بعد أن تكون قد حدّثته بالفعل.",
"شاشة التطبيقات — زر «إضافة عبر رابط» لم يعد يغطي آخر تطبيق في القائمة.",
"البحث لم يعد يُظهر نافذة تجاوز حد الطلبات في GitHub عندما يكون الخادم الخلفي مشغولاً — يعرض رسالة «حاول لاحقاً» مختصرة بدلاً من ذلك."
"البحث لم يعد يُظهر نافذة تجاوز حد الطلبات في GitHub عندما يكون الخادم الخلفي مشغولاً — يعرض رسالة «حاول لاحقاً» مختصرة بدلاً من ذلك.",
"نافذة «الجديد» وسجلّها يتحوّلان الآن إلى اللغة المختارة حديثاً دون الحاجة إلى إعادة تشغيل التطبيق."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"লিকুইড গ্লাস ইফেক্ট সরানো হয়েছে — ডার্ক মোডে স্বচ্ছ ব্যাকগ্রাউন্ডে আইকন অদৃশ্য হয়ে যেতে পারত। সর্বত্র পরিষ্কার, উচ্চ-কনট্রাস্ট UI।",
"স্টোর আপনি ইতিমধ্যে আপডেট করার পরে আর নিজের জন্য আপডেট আছে দাবি করে না।",
"Apps স্ক্রিন — «লিঙ্ক দিয়ে যোগ করুন» বোতাম আর তালিকার শেষ অ্যাপটি ঢেকে রাখে না।",
"ব্যাকএন্ড ব্যস্ত থাকলে সার্চ আর GitHub রেট-লিমিট ডায়ালগ দেখায় না — এর বদলে একটি 'পরে চেষ্টা করুন' টোস্ট দেখায়।"
"ব্যাকএন্ড ব্যস্ত থাকলে সার্চ আর GitHub রেট-লিমিট ডায়ালগ দেখায় না — এর বদলে একটি 'পরে চেষ্টা করুন' টোস্ট দেখায়।",
"'নতুন কী' শিট আর ইতিহাস এখন অ্যাপ পুনরায় চালু না করেই নতুন নির্বাচিত ভাষায় পরিবর্তিত হয়।"
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"Efecto de cristal líquido eliminado — los iconos en modo oscuro podían volverse invisibles sobre fondos transparentes. Interfaz más limpia y con mayor contraste en todas partes.",
"La tienda ya no anuncia una actualización propia disponible después de que la hayas actualizado.",
"Pantalla de Apps — el botón «Añadir por enlace» ya no tapa la última app de la lista.",
"La búsqueda ya no dispara el diálogo de límite de peticiones de GitHub cuando el backend está ocupado — muestra un toast amigable de «inténtalo en breve»."
"La búsqueda ya no dispara el diálogo de límite de peticiones de GitHub cuando el backend está ocupado — muestra un toast amigable de «inténtalo en breve».",
"La hoja de novedades y el historial ahora cambian al idioma recién seleccionado sin necesidad de reiniciar la app."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"Effet verre liquide supprimé — les icônes en mode sombre pouvaient devenir invisibles sur des fonds transparents. Interface plus nette et plus contrastée partout.",
"Le store ne signale plus une mise à jour disponible pour lui-même après que vous l'avez déjà mis à jour.",
"Écran Apps — le bouton « Ajouter par lien » ne masque plus la dernière app de la liste.",
"La recherche ne déclenche plus la boîte de dialogue de limitation de débit GitHub quand le backend est occupé — affiche un toast « réessayez bientôt » à la place."
"La recherche ne déclenche plus la boîte de dialogue de limitation de débit GitHub quand le backend est occupé — affiche un toast « réessayez bientôt » à la place.",
"La fiche des nouveautés et l'historique passent désormais à la langue nouvellement choisie sans avoir à redémarrer l'application."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"लिक्विड ग्लास इफ़ेक्ट हटा दिया गया — डार्क मोड में पारदर्शी पृष्ठभूमि पर आइकन अदृश्य हो सकते थे। हर जगह साफ़, उच्च-कंट्रास्ट UI।",
"स्टोर अब पहले से अपडेट होने के बाद अपने लिए अपडेट उपलब्ध होने का दावा नहीं करता।",
"Apps स्क्रीन — 'लिंक से जोड़ें' बटन अब सूची के आख़िरी ऐप को नहीं ढकता।",
"बैकएंड व्यस्त होने पर सर्च अब GitHub रेट-लिमिट डायलॉग नहीं दिखाती — इसके बजाय 'बाद में प्रयास करें' टोस्ट दिखाती है।"
"बैकएंड व्यस्त होने पर सर्च अब GitHub रेट-लिमिट डायलॉग नहीं दिखाती — इसके बजाय 'बाद में प्रयास करें' टोस्ट दिखाती है।",
"'क्या नया है' शीट और इतिहास अब ऐप पुनरारंभ किए बिना नई चुनी गई भाषा पर स्विच हो जाते हैं।"
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"Effetto vetro liquido rimosso — le icone in modalità scura potevano diventare invisibili su sfondi trasparenti. Interfaccia più pulita e con maggior contrasto ovunque.",
"Lo store non segnala più un aggiornamento disponibile per sé stesso dopo che lo hai già aggiornato.",
"Schermata App — il pulsante «Aggiungi tramite link» non copre più l'ultima app dell'elenco.",
"La ricerca non mostra più il dialogo di limite richieste di GitHub quando è il backend a essere occupato — mostra invece un toast «riprova a breve»."
"La ricerca non mostra più il dialogo di limite richieste di GitHub quando è il backend a essere occupato — mostra invece un toast «riprova a breve».",
"Il pannello Novità e la cronologia passano ora alla lingua appena selezionata senza dover riavviare l'app."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"リキッドグラス効果を削除しました — ダークモードでは透明な背景にアイコンが見えなくなることがありました。全体的にすっきりした、コントラストの高い UI になりました。",
"ストアを更新したあとも自分自身に更新があると表示し続ける問題を修正しました。",
"アプリ画面 — 「リンクで追加」ボタンが一覧の最後のアプリを覆わなくなりました。",
"バックエンドが混雑しているときに検索が GitHub のレート制限ダイアログを出さなくなりました — 代わりに「しばらくしてから再試行」のトーストを表示します。"
"バックエンドが混雑しているときに検索が GitHub のレート制限ダイアログを出さなくなりました — 代わりに「しばらくしてから再試行」のトーストを表示します。",
"「新着情報」シートと履歴がアプリを再起動しなくても新しく選択した言語に切り替わるようになりました。"
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"리퀴드 글라스 효과 제거 — 다크 모드의 투명 배경 위에서 아이콘이 보이지 않을 수 있었습니다. 모든 곳에서 더 깔끔하고 대비가 높은 UI로 개선되었습니다.",
"스토어를 업데이트한 뒤에도 자신에 대해 업데이트가 있다고 계속 알리던 문제를 수정했습니다.",
"앱 화면 — '링크로 추가' 버튼이 목록의 마지막 앱을 가리지 않습니다.",
"백엔드가 바쁠 때 검색이 더 이상 GitHub 속도 제한 대화상자를 띄우지 않습니다 — 대신 '잠시 후 다시 시도하세요' 토스트를 보여줍니다."
"백엔드가 바쁠 때 검색이 더 이상 GitHub 속도 제한 대화상자를 띄우지 않습니다 — 대신 '잠시 후 다시 시도하세요' 토스트를 보여줍니다.",
"'새로운 소식' 시트와 기록이 앱을 재시작하지 않아도 새로 선택한 언어로 전환됩니다."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"Efekt płynnego szkła usunięty — w trybie ciemnym ikony na przezroczystym tle mogły stawać się niewidoczne. Wszędzie czystszy, bardziej kontrastowy interfejs.",
"Sklep nie zgłasza już dostępnej aktualizacji dla samego siebie po jej zainstalowaniu.",
"Ekran Aplikacji — przycisk „Dodaj przez link” nie zasłania już ostatniej aplikacji na liście.",
"Wyszukiwarka nie wyświetla już okna limitu zapytań GitHuba, gdy backend jest zajęty — zamiast tego pokazuje przyjazny toast „spróbuj ponownie za chwilę”."
"Wyszukiwarka nie wyświetla już okna limitu zapytań GitHuba, gdy backend jest zajęty — zamiast tego pokazuje przyjazny toast „spróbuj ponownie za chwilę”.",
"Panel „Co nowego” i historia przełączają się teraz na nowo wybrany język bez konieczności restartu aplikacji."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"Эффект жидкого стекла убран — в тёмной теме иконки могли становиться невидимыми на прозрачном фоне. Везде более чистый и контрастный интерфейс.",
"Магазин больше не сообщает о доступном обновлении самого себя после того, как вы его уже обновили.",
"Экран приложений — кнопка «Добавить по ссылке» больше не закрывает последнее приложение в списке.",
"Поиск больше не показывает диалог превышения лимита GitHub, когда занят сам бэкенд — вместо этого выводит дружелюбный тост «повторите позже»."
"Поиск больше не показывает диалог превышения лимита GitHub, когда занят сам бэкенд — вместо этого выводит дружелюбный тост «повторите позже».",
"Окно «Что нового» и история теперь переключаются на выбранный язык без перезапуска приложения."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"Sıvı cam efekti kaldırıldı — koyu modda ikonlar şeffaf arka planlarda görünmez olabiliyordu. Her yerde daha temiz, daha kontrastlı bir arayüz.",
"Mağaza artık siz güncelledikten sonra kendisi için güncelleme mevcut olduğunu söylemiyor.",
"Uygulamalar ekranı — 'Bağlantıyla ekle' düğmesi artık listenin son uygulamasını örtmüyor.",
"Backend meşgulken arama artık GitHub hız sınırı diyaloğunu açmıyor — bunun yerine 'birazdan tekrar deneyin' bildirimi gösteriyor."
"Backend meşgulken arama artık GitHub hız sınırı diyaloğunu açmıyor — bunun yerine 'birazdan tekrar deneyin' bildirimi gösteriyor.",
"'Yenilikler' paneli ve geçmiş, uygulamayı yeniden başlatmaya gerek kalmadan yeni seçilen dile geçiş yapıyor."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"移除液态玻璃效果 — 在深色模式下,透明背景上的图标可能会看不清。整体界面更干净、对比度更高。",
"更新商店后,商店不会再继续提示自己有新版本可用。",
"应用页面 — 「通过链接添加」按钮不再遮挡列表中最后一个应用。",
"后端繁忙时搜索不再弹出 GitHub 速率限制对话框 — 而是显示「请稍后重试」的提示。"
"后端繁忙时搜索不再弹出 GitHub 速率限制对话框 — 而是显示「请稍后重试」的提示。",
"「新功能」面板和历史记录现在会跟随新选语言切换,无需重启应用。"
]
}
]
Expand Down