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>
102 lines
6.1 KiB
Markdown
102 lines
6.1 KiB
Markdown
# 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
|
|
|
|
- [x] 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.
|
|
- [x] A repository not starred still shows outlined, as before.
|
|
- [x] 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.
|
|
- [x] Pull-to-refresh re-derives the starred set fresh rather than reusing a stale cache.
|
|
- [x] Build, unit tests, and lint pass.
|
|
- [x] 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
|
|
|
|
```text
|
|
./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 — no Codex review pending (Codex retired, 2026-09-29).
|