Ubisam-MobileGit/tasks/037-already-starred-repos-show-filled-on-load.md
choiminyeong 683305e6c8 TASK-037: already-starred repositories show filled on the ALL list's first load
Closes a limitation accepted since TASK-018/TASK-030: a repository
already starred before the current session showed outlined until
directly toggled. loadStarredCoordinatesOnce fetches the account's
full starred set once per session (one request for the whole list, not
N+1 per row) via the same /user/starred endpoint already used for the
FAVORITES tab, mirroring loadOrganizationsOnce's caching shape.
toggleFavorite keeps the cache in sync on success; refreshRepositories
and logout invalidate/clear it the same way the equivalent
repositorySnapshots/cachedOrganizations caches already are.

Live-verified: a repository independently confirmed starred via the
FAVORITES tab showed filled on the ALL list immediately after a fresh
app launch, no interaction needed, screenshot-confirmed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:36:15 +09:00

6.0 KiB

TASK-037: Already-starred repositories show filled on the ALL list's first load

Status: implemented; all required checks pass; live-confirmed on the human product owner's device Owner: Claude Reviewer: Codex

Outcome

Closes a limitation accepted (not fixed) all the way back in TASK-018/TASK-030: a repository already starred before the current app session started an ALL-list session showing outlined (unstarred) until the human product owner directly tapped it. The ALL list now cross-references the account's actual starred set once per session and shows a filled star from the very first load for any repository that's genuinely already starred server-side.

Scope

  • Included: feature/repositories/RepositoryListViewModel.kt (loadStarredCoordinatesOnce, toUiModel gains an alreadyStarred parameter, toggleFavorite keeps the new cache in sync, refreshRepositories/logout invalidate/clear it), matching test additions in RepositoryListViewModelTest.kt.
  • Excluded: No change to the FAVORITES list (every row there is inherently already starred, and FavoriteStar's rendering for that mode never consulted favoriteAction in the first place — see RepositoryListScreen.kt's existing doc comment). No per-row network call — this is one list fetch for the whole page, not N+1 (see Design notes).

Design notes

  • One request for the whole list, not one per row: Gitea's plain repository-list response has no "is this starred by me" field, but the account's full starred set is already reachable via the same /user/starred endpoint this app already calls for the FAVORITES tab (favoritesApi.listStarred). loadStarredCoordinatesOnce fetches and caches that whole set once per session (mirroring loadOrganizationsOnce's exact shape: cache-check, try/catch with a non-fatal empty fallback on failure, cache the result either way) rather than calling Gitea's per-repository IsStarring endpoint once per row, which would turn a single list load into N+1 requests.
  • The cache stays self-consistent without extra network calls: toggleFavorite's success paths now also update starredCoordinatesCache directly (+/- the toggled repository) instead of leaving it stale until the next full refetch — so switching between ALL/FAVORITES, paging, or reloading later in the same session keeps reflecting what this session itself just did, not just what the very first load saw.
  • Invalidated exactly where the equivalent repositorySnapshots cache already is: refreshRepositories() (pull-to-refresh) clears it alongside the snapshot removal it already does, and logout() clears it alongside cachedOrganizations/repositorySnapshots. Deliberately not cleared in clearSessionOnAuthenticationExpired(), matching that those two existing caches aren't cleared there either — consistency with the established (if arguably imperfect) precedent was chosen over introducing a new asymmetry just for this one cache.

Acceptance criteria

  • A repository the account has already starred (independent of this app session) shows a filled, primary-colored star on the ALL list's very first load, with no prior interaction.
  • A repository not starred still shows outlined, as before.
  • Toggling a star/unstar keeps working exactly as TASK-034 left it, and additionally keeps the new cache consistent for any later reload within the same session.
  • Pull-to-refresh re-derives the starred set fresh rather than reusing a stale cache.
  • Build, unit tests, and lint pass.
  • Live-confirmed on the human product owner's device: a repository independently verified as starred via the FAVORITES tab (LGE-OC6-Inline-CylinderTypeWelder, ★ 1) showed filled on the ALL list immediately after a fresh app launch, screenshot-verified.

Required checks

./gradlew compileDebugKotlin compileDebugUnitTestKotlin
./gradlew testDebugUnitTest
./gradlew assembleDebug lintDebug

Evidence

  • RepositoryListViewModel.kt: new starredCoordinatesCache field; new loadStarredCoordinatesOnce(); loadRepositoryPage computes an alreadyStarred set for ALL-mode loads and passes it through toUiModel(alreadyStarred = ...); toggleFavorite's success paths update the cache; refreshRepositories()/ logout() invalidate/clear it.
  • RepositoryListViewModelTest.kt: two new tests — a repository already starred shows Starred (filled) on first load while an unstarred sibling shows Idle; un-starring an already-session- known-starred row and then reloading the ALL list (via showAllRepositories()) shows Idle, proving the cache itself (not just the immediately-visible row) was updated. All 28 tests in this file pass, including every pre-existing one, unmodified.
  • ./gradlew compileDebugKotlin compileDebugUnitTestKotlin: BUILD SUCCESSFUL.
  • ./gradlew testDebugUnitTest: BUILD SUCCESSFUL, all unit tests passing.
  • ./gradlew assembleDebug lintDebug: BUILD SUCCESSFUL, 0 lint errors.
  • Installed on the human product owner's device over wireless adb. Live-verified: confirmed RobotAutomationDevelopment-Dept/LGE-OC6-Inline-CylinderTypeWelder was genuinely starred server-side via the FAVORITES tab (★ 1 shown there), then located the same repository on the ALL list (page 2) after a fresh app launch and confirmed it showed a filled green ★ 1 immediately, with no tap needed — screenshot-verified, no crash in logcat.

Known limitations

  • Still session-scoped, not persisted across app restarts independent of this cache's own invalidation rules — this was always going to require re-fetching the starred set on a fresh session anyway, since Gitea (not this app) is the source of truth for who has starred what.
  • If the account's starred set changes from somewhere else (the Gitea web UI, another device) mid-session, the ALL list won't reflect that until the next refreshRepositories() (pull-to-refresh) or a full logout/login — the same staleness window every other cached list in this app already has.

Review findings

None yet — pending Codex/human review.