Skip to content

fix: don't wipe the current server when a switch target is unreachable - #299

Merged
van-geaux merged 1 commit into
van-geaux:mainfrom
chortya:up/fix-server-switch-data-loss
Oct 19, 2026
Merged

van-geaux merged 1 commit into
van-geaux:mainfrom
chortya:up/fix-server-switch-data-loss

Conversation

@chortya

@chortya chortya commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Contribution pause

The maintainer lifted the pause and invited split PRs in #286.

  • The change is scoped to one problem (server switching destroying local state).
  • Compatible with the provider-module refactor (AppCoordinator flow plus one BookOrbit cache fallback; provider-aware for BookOrbit, Komga and Kavita profiles).
  • Tests and manual-validation status are documented.
  • No credentials, cookies, API keys, private server URLs, or generated local data are included.

Summary

Choosing a configured server while it can't be reached destroys the offline library. This happened on a real device: home-only DNS was bypassed by a phone VPN or Private DNS.

AppCoordinator.changeServer() clears the current server before checking the target: session, unsynced progress/session/annotation queues, catalog snapshots, reader positions and the saved server URL. The login/setup screens' Configured servers list calls it even for the server already in use. Tapping it while offline therefore wipes everything, and the app reopens on the setup screen even after a restart.

Change Effect
Same URL and provider → bootstrap() instead of a switch Reconnects with the normal offline fallback; nothing is cleared
Target checked before anything is cleared An unreachable or mistyped server shows the setup error; the current server stays intact
saveServer() goes through the full switch while a server is still configured A setup retry after a failed switch can't carry the old session over to the new server
No server configured + an unreachable saved profile → restore it and start offline Downloads remain usable offline; progress reporting re-enabled
BookOrbitRepository.loadCachedBrowserState: no catalog snapshot → offline library from downloaded books No dead-end login screen offline

Verification

  • ./gradlew :app:testDebugUnitTest — 949/949 pass. :app:lintDebug — 0 errors. :app:assembleDebug succeeds. No added trailing whitespace; line endings preserved.
  • AppCoordinatorTest:
    • change server leaves the replacement prefilled when its validation fails previously required the destructive clear (clearServerCalls == 1, serverUrl == null). It now requires that nothing is cleared.
    • New: same-server reconnect (no check, no clear), provider change at the same URL, setup retry after a failed switch.
    • The new tests fail on main.
  • New OfflineServerSwitchInstrumentedTest (emulator, real repository and profile store, an .invalid host):
    • an unreachable current server keeps its offline library;
    • a cleared saved profile is restored with its downloads.
    • Both fail on main and pass with the fix, requiring the screen to stay stable for 3 s.
  • BookOrbitAppInstrumentedTest shows the same failures with and without this change on a Pixel Fold API 36 emulator (32 of 47 fail on main there).
  • Peer review (Codex GPT-6-Astra) found the token carry-over through setup "Continue", disabled progress after an offline restore, a provider-blind shortcut, and two weak tests. All fixed; the re-review found no remaining issues.

🤖 Generated with Claude Code

changeServer() cleared the current server (session, unsynced progress
and annotation queues, catalog snapshots, reader positions, saved server
URL) before checking the target. The login/setup "Configured servers"
list calls it even for the server already in use, so tapping it while
offline, or with DNS that only resolves at home, destroyed the offline
library and left the app on the setup screen.

- Choosing the server already in use (same URL and provider) reconnects
  through bootstrap() instead of switching.
- The target is checked before anything is cleared; a failed check
  shows the setup error and leaves the current server intact.
- saveServer() routes through the full switch while a server is still
  configured, so the old session never carries over to a new server.
- With no server configured, an unreachable saved profile is restored
  and opens offline (progress reporting re-enabled).
- Without a catalog snapshot, the offline library offers downloaded
  books instead of a login screen that cannot reach the server.

Tests: AppCoordinatorTest (failed switch keeps the server, same-server
reconnect, provider change at the same URL, setup retry after a failed
switch); OfflineServerSwitchInstrumentedTest (unreachable current server
keeps its offline library; a cleared saved profile is restored with its
downloads). Each fails on the previous code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@van-geaux

Copy link
Copy Markdown
Owner

@chortya Thanks for the focused fix and for adding coverage around unreachable servers, same-server reconnects, failed switches, and restoring downloaded books offline. Preventing a failed switch from wiping the current server’s local state is an important improvement.

One possible edge case to examine during production validation is when two saved profiles use the same server URL but different providers. The offline restore path currently matches by URL and not provider.

This is unlikely in normal use because different providers generally use different URLs, but we will test it to confirm that the correct provider’s cached state and downloads are restored.

Other than that possible edge case, the PR looks good. We will merge it and examine this scenario in production.

@van-geaux
van-geaux merged commit 65d712d into van-geaux:main Oct 19, 2026
@chortya

chortya commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for merging! I looked into the provider edge case. Profiles are keyed by URL, so two saved profiles can't share an address, but choosing a saved profile's address with a different provider while offline did restore that profile under the wrong provider. The fix (provider must match too, with a regression test) is in #306.

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.

2 participants