Skip to content

fix: retain library navigation context and review flows - #180

Merged
cnhy-nero-diskard merged 23 commits into
masterfrom
fix/navigation-context-flows
Oct 8, 2026
Merged

cnhy-nero-diskard merged 23 commits into
masterfrom
fix/navigation-context-flows

Conversation

@cnhy-nero-diskard

Copy link
Copy Markdown
Owner

Summary

Retains Library discovery context across detail/review returns, tab switches, and short background departures (5-minute visit), fixes Library bottom insets and top-level bar visibility, extends Library discovery (wishlist search, Added recently sort, concise Hide dialog), and makes HLTB review progression forward-only with session Skip plus grid and Settings entry points.

Covers feedback threads #157 (navigation/insets), #167 (library visit + discovery), and #163 (HLTB review jump) from index #174.

Changes

  • Bottom bar shown only on Home, Library, History, Analytics, Settings overview; Library bottom gap removed in list and both grids for gesture and three-button nav.
  • Library visit state holder preserves query, filters, and scroll anchor across pushed routes, recreation, and backgrounding; expiry at 5 minutes resets to top.
  • Wishlist title matches surfaced in Library search with store actions; Added recently sort using first-observed times; condensed Hide confirmation retaining restore guidance and XP/level effects.
  • HLTB reviewer adds route-session Skip/deferral, forward-only next selection, exhaustion and Review skipped states, per-game actions in both grids, and Settings Gameplay Match Center shortcut.
  • OpenSpec: 01-fix-library-navigation-insets, 02-retain-library-visit-context, 03-extend-library-discovery, 04-improve-hltb-review-flow (all tasks checked, strict validation passed).

Release note

  • Library search and scroll position survive detail returns, tab switches, and short app backgrounding.
  • Library bottom spacing and bottom-bar visibility fixed across navigation modes and pushed screens.

Testing

  • Focused unit tests pass (library visit/scroll/sort/wishlist, HLTB session/selection, repository checks)
  • :app:assembleDebug\ and :app:assembleDebugAndroidTest\ pass
  • :app:lintDebug\ passes (0 errors, baseline warnings)
  • ./gradlew test\ full suite (known Windows DataStore temp-dir isolation flake in unrelated cloud/settings tests; focused suites green)
  • Roborazzi: no golden updates in this PR (instrumented fixture hosts used for verification; visual coverage degraded to single phone/dark theme)
  • Manually verified on device/emulator (Xiaomi degas API 36 + API 35 emulator per verification.md)

Notes

  • Branch is checked out in worktree at \C:/Users/cnhyn/.codex/worktrees/3810/Backlogium; this PR was opened without switching the main worktree off \ ix/stabilize-first-run-setup.
  • Verification docs: \

@cnhy-nero-diskard cnhy-nero-diskard self-assigned this Oct 7, 2026

@cnhy-nero-diskard cnhy-nero-diskard left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Consolidated finding is inline.

@cnhy-nero-diskard cnhy-nero-diskard left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Implementation finding is inline.

Comment thread app/src/main/java/com/example/backlogium/ui/library/LibraryScreen.kt Outdated

@cnhy-nero-diskard cnhy-nero-diskard left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Consolidated implementation finding is inline.

@@ -287,9 +254,7 @@ class HltbReviewViewModel @Inject constructor(
*/
fun resolve(appId: Long, candidate: HltbCandidate) = viewModelScope.launch {
hltbRepository.resolveMatch(appId, candidate)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

!RESOLVE The reviewer protects stale-frame Skip/Next/Previous actions with expectedAppId, but match confirmation is still an unconditional persistence write, so an old UI frame can overwrite newer HLTB state. HltbRepository.resolveMatch() does a read followed by upsert() with no commit-time eligibility/version check. Repro: game A is visible as NEEDS_REVIEW, then another in-flight refresh/manual path updates A (for example resolves it automatically or replaces its candidate set) before Compose renders the queue emission; tapping a candidate from the stale frame still calls this method with A and overwrites the newer row. The same problem exists in confirmManualLink(), which eventually calls the same unconditional resolver. This is exactly the class of race the new session code already guards for stale Skip/Next/Previous, and broader search already avoids with a conditional DAO update. Make manual/candidate resolution atomic and conditional on the row version/state the UI actually reviewed (status alone is insufficient if NEEDS_REVIEW can be replaced with a newer NEEDS_REVIEW candidate set), return whether the write was accepted, and let a rejected stale action follow the current queue instead of overwriting it. Add a regression test where the row changes after the frame is produced but before candidate/manual confirmation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Resolved in commit cb7c4dd. Applied the requested review fix. Validation: configured commands passed.

@cnhy-nero-diskard cnhy-nero-diskard left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Follow-up implementation finding is inline.

): LazyListState {
val items = remember(state, wishlist) { libraryScrollItems(state, wishlist) }
// No rememberSaveable: a newly launched process must never resurrect navigation's saved scroll.
val listState = remember(visit, generation, state.density) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

!RESOLVE [P2] Restoring a retained Wishlist scroll anchor can silently land on (and then save) the wrong Library row when Wishlist state arrives after the owned-library state. LibraryScreen only waits for LibraryUiState to finish loading, but WishlistViewModel.uiState starts as WishlistUiState(configured=false, entries=[]) and hydrates independently. If the saved visit.scrollAnchor.gameId belongs to a wishlist-only search result, the first libraryScrollItems(state, wishlist) omits that entire section. resolveLibraryScroll() falls back to the old numeric index, and the initial snapshotFlow capture overwrites the correct wishlist identity with whatever row occupies that index. When Wishlist emits its cached entries, remember(visit, generation, state.density) keeps the already-created LazyListState, so the original game is never re-anchored. Repro: search, scroll to a wishlist result, leave Library (e.g. Back to Home), reopen within the 5-minute visit while delaying the fresh Wishlist state emission; the return is at an unrelated row. Defer/reconcile restoration until the relevant Wishlist rows are hydrated, and do not capture a fallback as a new anchor before that reconciliation. Add a Compose regression test with a retained wishlist-only anchor and initially unconfigured/empty Wishlist state followed by cached results.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Resolved in commit fb0ae39. Applied the requested review fix. Validation: configured commands passed.

@cnhy-nero-diskard
cnhy-nero-diskard merged commit 8b48146 into master Oct 8, 2026
8 checks passed
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