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>
114 lines
7.6 KiB
Markdown
114 lines
7.6 KiB
Markdown
# 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`'s `toggleFavorite` — patches
|
|
`repositorySnapshots` (not just `starredCoordinatesCache`) on every successful star/unstar; two new
|
|
private helpers (`adjustCachedStarCounts`, `removeFromCachedSnapshot`); the FAVORITES-mode unstar
|
|
branch now also updates `starredCoordinatesCache` (previously only the ALL-mode branches did).
|
|
- Excluded: no change to how `repositorySnapshots`/`starredCoordinatesCache` are 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 own `starsCount` on the
|
|
plain repository-listing endpoint lags slightly behind a star/unstar (the `/user/starred` listing
|
|
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 way `starredCoordinatesCache` was 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 full `RepositorySummary`, which `toggleFavorite` only has as a `RepositoryRowUiModel` (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.
|
|
- **`starredCoordinatesCache` now 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
|
|
|
|
- [x] Starring a repository from the ALL tab, then switching to FAVORITES, shows the newly-starred
|
|
repository there (not silently missing).
|
|
- [x] Switching back to ALL after that shows the correct, incremented star count — not reverted to the
|
|
pre-toggle cached value.
|
|
- [x] Unstarring (from either tab) keeps the count/membership consistent across the same round trip.
|
|
- [x] Every existing favorite-toggle test still passes unmodified.
|
|
- [x] 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.
|
|
- [x] Build, unit tests, and lint pass.
|
|
- [x] 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
|
|
|
|
```text
|
|
./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug
|
|
```
|
|
|
|
## Evidence
|
|
|
|
- `feature/repositories/RepositoryListViewModel.kt`: `toggleFavorite`'s three success branches now
|
|
each patch `repositorySnapshots` via the new `adjustCachedStarCounts`/`removeFromCachedSnapshot`
|
|
helpers, alongside the existing `starredCoordinatesCache` patch (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 back` and `un-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.w` logging of the star/unstar HTTP status directly
|
|
in `GiteaRepositoryBrowserApi.star`/`unstar` to 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`. Starred `LGD-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).
|