No task waits on a Codex review; the redesign tasks are confirmed on the product owner's Z Fold7; TASK-016's leftovers are on hold; no biometric reauthentication. Agent: main (opus) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.6 KiB
TASK-041: Favorite star reverted / missing from favorites after switching tabs
Status: implemented; all required checks pass; live-confirmed on the human product owner's device Owner: Claude Reviewer: Codex
Outcome
Live bug report: "즐겨찾기 누르고나면 카운팅은 되는데 즐겨찾기 탭에 가면 방금 누른 항목이 안떠있다. 그리고 다시 원래 탭인 레포지토리로 가보면 다시 즐겨찾기가 풀려있어" (starring a repository counts up, but it doesn't show up on the favorites tab, and going back to the repository tab the star looks undone again).
Root cause: RepositoryListViewModel caches each mode's full repository list once per session
(repositorySnapshots, keyed by mode/organization) and reuses it on every later visit to that mode
without refetching. toggleFavorite's success path already kept a second, separate cache
(starredCoordinatesCache, used only for the ALL list's "is this already starred" cross-reference) in
sync with a successful star/unstar — but never touched repositorySnapshots itself. So after starring
a repository from the ALL tab: the star icon correctly showed filled (from the patched
starredCoordinatesCache), but its starsCount reverted to whatever repositorySnapshots had cached
from the very first ALL-tab load, before the star happened — and the FAVORITES tab, if its snapshot was
already cached from an earlier visit, kept showing its own pre-star list, silently missing the new
star. Confirmed live: the actual PUT /api/v1/user/starred/... call was succeeding (HTTP 204, added
temporary debug logging to verify), and Gitea's /user/starred listing reflected the toggle almost
immediately, but its plain repository-listing endpoint (/user/repos) was observed to lag behind on
starsCount for a bit — both real UI symptoms trace to this same repositorySnapshots gap.
Scope
- Included:
feature/repositories/RepositoryListViewModel.kt'stoggleFavorite— patchesrepositorySnapshots(not juststarredCoordinatesCache) on every successful star/unstar; two new private helpers (adjustCachedStarCounts,removeFromCachedSnapshot); the FAVORITES-mode unstar branch now also updatesstarredCoordinatesCache(previously only the ALL-mode branches did). - Excluded: no change to how
repositorySnapshots/starredCoordinatesCacheare populated or invalidated elsewhere (refreshRepositories(),logout()already clear both correctly — untouched). No change to the actual star/unstar HTTP calls themselves (confirmed already correct).
Design notes
- A local patch, not a cache-clearing forced refetch. The tempting alternative — just call
repositorySnapshots.clear()on every successful toggle, forcing a fresh fetch next time each mode is opened — was tried first and reverted after live testing showed Gitea's ownstarsCounton the plain repository-listing endpoint lags slightly behind a star/unstar (the/user/starredlisting reflected a toggle before the plain repository listing did). Forcing a refetch would trade one race condition (a frozen-forever local cache) for another (trusting a server response that hasn't caught up yet) — and would have undone this app's own established "the client already knows the truth, don't wait on the server to agree" philosophy from TASK-034's optimistic-UI fix. Patching the cached count directly, the same waystarredCoordinatesCachewas already being patched, keeps the display correct immediately and doesn't depend on Gitea's timing at all. - The FAVORITES snapshot's membership (not just count) is different: removing a repository from
a cached FAVORITES snapshot on unstar is a simple filter (
removeFromCachedSnapshot) since no new data is needed. Adding one on star can't be done the same way — inserting a row into that cached list would need a fullRepositorySummary, whichtoggleFavoriteonly has as aRepositoryRowUiModel(a UI projection, not the domain type, and not guaranteed to carry every field). Rather than reconstructing one and risking a subtly-wrong synthetic entry, a star instead invalidates just the FAVORITES snapshot key (repositorySnapshots.remove(...)) if it was already cached, so the next visit fetches it fresh — a single, targeted extra network call only when that specific list was already stale, not a blanket invalidation of everything. starredCoordinatesCachenow updates on the FAVORITES-mode unstar branch too. It previously only updated on the two ALL-mode branches — a real, separate gap: unstarring from the FAVORITES tab left the ALL tab's "already starred" cross-reference stale for that repository until the next full session-cache invalidation.
Acceptance criteria
- Starring a repository from the ALL tab, then switching to FAVORITES, shows the newly-starred repository there (not silently missing).
- Switching back to ALL after that shows the correct, incremented star count — not reverted to the pre-toggle cached value.
- Unstarring (from either tab) keeps the count/membership consistent across the same round trip.
- Every existing favorite-toggle test still passes unmodified.
- Two new regression tests cover the exact reported scenario (star-then-switch-tabs-and-back, unstar-then-switch-tabs-and-back), both asserting the count stays correct after the round trip.
- Build, unit tests, and lint pass.
- Live-confirmed on the human product owner's device: starred a never-before-touched repository, confirmed it appeared correctly in 즐겨찾기, switched back to 전체 저장소, confirmed the star and count (★1) both stayed correct instead of reverting.
Required checks
./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug
Evidence
feature/repositories/RepositoryListViewModel.kt:toggleFavorite's three success branches now each patchrepositorySnapshotsvia the newadjustCachedStarCounts/removeFromCachedSnapshothelpers, alongside the existingstarredCoordinatesCachepatch (now present on all three branches, not just the two ALL-mode ones).RepositoryListViewModelTest.kt: two new tests —starring a repository keeps its incremented count after switching to favorites and backandun-starring keeps its decremented count after switching to favorites and back— plus every pre-existing favorite-toggle test (increment, repeat-tap-toggle, round-trip, immediate-optimistic-update, rollback-on-failure for both directions, the existing already-session-known-starred-cache test) passing unmodified.- Live debugging: temporarily added debug-only
Log.wlogging of the star/unstar HTTP status directly inGiteaRepositoryBrowserApi.star/unstarto confirm the actual server response (HTTP 204, i.e. success) before concluding the bug was client-side caching, not a failed request; removed once the root cause was confirmed. ./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug: BUILD SUCCESSFUL, all unit tests passing (including the two new ones), 0 lint errors.- Live device: installed over wireless
adb. StarredLGD-AP4-RPR(a repository never previously touched this session) from the ALL tab (screenshot: ★1). Switched to 즐겨찾기 — confirmed it appears there (screenshot: ★1, alongside the other already-starred repositories). Switched back to 전체 저장소 — confirmed it still shows ★1, not reverted to ☆0/★0 as before the fix (screenshot).
Known limitations
None outstanding for this task.
Review findings
None yet — no Codex review pending (Codex retired, 2026-09-29).