Skip to content

fix(whatsnew): re-load entries when app language changes#526

Merged
rainxchzed merged 2 commits into
mainfrom
fix/whatsnew-locale-refresh
May 6, 2026
Merged

fix(whatsnew): re-load entries when app language changes#526
rainxchzed merged 2 commits into
mainfrom
fix/whatsnew-locale-refresh

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 6, 2026

Bug

Switching the app language in Tweaks left the What's-new sheet and the Profile → What's new history showing the previous language's text.

Root cause

`WhatsNewViewModel.init` loaded entries once into `_historyEntries` and `_pendingEntry` StateFlows and never re-loaded. Even though the underlying `WhatsNewLoader` resolves the locale per call (so a fresh load would pick up the new language), the cached VM state served the previous language's strings until the VM was destroyed.

On Android the symptom is permanent until process death — the Activity recreates on language change but the ViewModelStore (and therefore the VM instance) survives `recreate()`. On Desktop language changes already require a restart, so the bug doesn't surface there in practice; this PR's behaviour is a no-op on the cold-start path it already took.

Fix

`WhatsNewViewModel` now collects `tweaksRepository.getAppLanguage().distinctUntilChanged()` and re-runs both `loadAll()` (history) and `evaluate()` (pending sheet) on every emission. `evaluate()` keeps its existing `lastSeen >= current` short-circuit so language changes after the user dismisses the sheet don't re-pop it.

Test plan

  • Open the app, dismiss the auto-shown what's-new sheet, switch language in Tweaks → no re-pop, history list refreshes when reopened.
  • Open the app pre-dismiss, switch language while the sheet is still up → sheet content swaps to the new language live.
  • Profile → What's new history → switch language → list refreshes.
  • Desktop: regression check that language change still surfaces the existing "restart required" snackbar (this PR doesn't change Desktop behaviour).
  • What's-new sheet on next 1.8.1 install lists the fix bullet in device language.

Summary by CodeRabbit

  • Bug Fixes

    • Improved backend busy state handling—now displays a friendly retry message instead of a rate-limit dialog.
  • Improvements

    • What's New sheet and history now dynamically update to reflect newly selected language without requiring an app restart.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Walkthrough

WhatsNewViewModel is refactored to observe app-language changes and dynamically reload What's New history and state, rather than preloading once at startup. Coroutine flow patterns are added to subscribe to language changes, and loading logic is extracted into reusable helper functions. What's New documentation files in 13 languages are updated to document the two new fixes: backend-busy toast behavior and dynamic language switching without app restart.

Changes

What's New Dynamic Language Switching

Layer / File(s) Summary
Reactive Architecture
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt (imports & observer)
Added flow imports (collect, distinctUntilChanged). Replaced initialization block with a coroutine-based observer that subscribes to tweaksRepository.getAppLanguage() and reloads history and pending state on language changes.
Loading Helpers
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt (reloadHistory, reloadPending)
Extracted reloadHistory() suspending function to load all WhatsNew entries and update _historyEntries and _hasHistory. Extracted reloadPending() suspending function to call evaluate() with error logging.
Documentation
core/presentation/src/commonMain/composeResources/files/whatsnew/*
Updated What's New release notes in 13 languages (en, ar, bn, es, fr, hi, it, ja, ko, pl, ru, tr, zh-CN). Added two fixed-item bullets per language: (1) backend-busy state now shows a friendly toast instead of GitHub rate-limit dialog, (2) What's New sheet and history switch to newly-selected language without app restart.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • OpenHub-Store/GitHub-Store#497: Refactors WhatsNewViewModel's history and loading state management into StateFlows and introduces WhatsNewLoader, which directly supports the language-change observer pattern added in this PR.

Poem

🐰 Language flows like a gentle stream,
No restart needed—watch it gleam!
The What's New sheet now understands,
When tongues must switch across all lands.
A hop, a flow, a reload sweet—
Where app and user's dreams shall meet! 🌍✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: reloading what's-new entries when the app language changes, which directly addresses the root cause of the bug where language-switched content was not updated.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/whatsnew-locale-refresh

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with 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.

Inline comments:
In
`@composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt`:
- Around line 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.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4bd239dd-6c53-4c17-87cf-059d381c56ea

📥 Commits

Reviewing files that changed from the base of the PR and between fde598f and b2b9674.

📒 Files selected for processing (14)
  • composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt
  • core/presentation/src/commonMain/composeResources/files/whatsnew/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/16.json

Comment on lines 49 to +50
} 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" }
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.

@rainxchzed rainxchzed merged commit d0c917f into main May 6, 2026
1 check passed
@rainxchzed rainxchzed deleted the fix/whatsnew-locale-refresh branch May 6, 2026 08:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant