The prior fix for double-counting used an early-return guard that made a repeat tap on an already-Starred row do nothing -- fixed the count bug but also made the star impossible to undo from the ALL list. Replaced it with a real toggle: an already-starred-this-session tap now calls unstar() and decrements the count back down instead of being inert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
105 lines
6.4 KiB
Markdown
105 lines
6.4 KiB
Markdown
# TASK-034: Star count doesn't increment, then repeat tap doesn't un-star
|
|
|
|
Status: implemented; all required checks pass; live-installed, awaiting the human product owner's confirmation
|
|
Owner: Claude
|
|
Reviewer: Codex
|
|
|
|
## Outcome
|
|
|
|
Two rounds of live feedback on the same feature, following TASK-033's OAuth scope fix. First: the
|
|
human product owner confirmed starring itself now works ("별표 눌러서 즐겨찾기는 되는데") but the
|
|
displayed star count didn't go up ("카운팅은 안올라가네") — `toggleFavorite`'s ALL-list success
|
|
branch updated only `favoriteAction` (to `Starred`) and never touched `starsCount`. Fixed by
|
|
incrementing `starsCount` in the same state update. Second, immediately after: with a naive
|
|
repeat-tap guard in place, tapping an already-starred row no longer un-starred it either
|
|
("이제 올라가는데 다시 눌렀을때 취소가 안되네") — the first fix's guard made a repeat tap a dead
|
|
no-op instead of a real toggle. Replaced that guard with an actual toggle: a repeat tap on a
|
|
row this session knows is starred now calls `unstar()` and decrements the count back down.
|
|
|
|
## Scope
|
|
|
|
- Included: `feature/repositories/RepositoryListViewModel.kt` (`toggleFavorite` only), matching
|
|
test additions in `RepositoryListViewModelTest.kt`.
|
|
- Excluded: No change to the FAVORITES-list unstar path (removing the row entirely already made
|
|
its count irrelevant, no bug there). No change to how the initial list load populates
|
|
`starsCount` (that already came from Gitea's own response, unaffected). No attempt to fetch the
|
|
server's authoritative post-star/unstar count instead of adjusting it client-side (see Design
|
|
notes).
|
|
|
|
## Design notes
|
|
|
|
- **Optimistic ±1, not a re-fetch**: Gitea's star/unstar endpoints (`PUT`/`DELETE`, 204 No Content)
|
|
don't return the new count, so getting the authoritative number back would mean a second network
|
|
call purely to update one number — not worth it for a value the app already knows how to derive
|
|
from something it just confirmed itself (the action succeeded, so the count is exactly one
|
|
higher or lower than what was last displayed). `starsCount` is floored at 0 via
|
|
`coerceAtLeast(0)` as a defensive guard, not because normal use can reach it.
|
|
- **A real toggle, not a guard**: the first pass at this fix added an early-return guard that made
|
|
a repeat tap on an already-`Starred` row do nothing — that stopped the double-count bug but also,
|
|
as immediately reported, made the star impossible to undo from the ALL list without switching to
|
|
the FAVORITES tab. The actual fix branches on the row's own `favoriteAction`: not-yet-starred-this-
|
|
session taps call `star()` (`+1`, → `Starred`), already-starred-this-session taps call
|
|
`unstar()` (`-1`, → `Idle`) — this both fixes the double-count and restores the obviously-expected
|
|
behavior of tapping a filled star to unfill it.
|
|
- **The underlying "can't tell if already starred on load" limitation is untouched**: this toggle
|
|
only tracks what happened *within this session's own interaction* with a row, same as
|
|
`favoriteAction` always has (TASK-018/TASK-030). A repository already starred before the app
|
|
session started still shows outlined/untapped until acted on directly.
|
|
|
|
## Acceptance criteria
|
|
|
|
- [x] Starring a repository from the ALL list increments its displayed star count by exactly 1.
|
|
- [x] Tapping an already-`Starred` row again un-stars it: calls `unstar()`, decrements the count by
|
|
1, and returns the row to `Idle` (outlined, tappable to star again).
|
|
- [x] The count never displays below 0.
|
|
- [x] Unstarring from the FAVORITES list is unaffected (row removal, no count to update).
|
|
- [x] Build, unit tests, and lint pass.
|
|
- [ ] Live confirmation from the human product owner that both starring (count up) and un-starring
|
|
(count back down) now work as expected from the ALL list — installed on the device but not
|
|
yet re-tested in this exact session.
|
|
|
|
## Required checks
|
|
|
|
```text
|
|
./gradlew compileDebugKotlin compileDebugUnitTestKotlin
|
|
./gradlew testDebugUnitTest
|
|
./gradlew assembleDebug lintDebug
|
|
```
|
|
|
|
## Evidence
|
|
|
|
- `RepositoryListViewModel.kt`: `toggleFavorite` now branches on whether the row is already
|
|
`Starred` this session — un-star path (`unstar()`, `starsCount - 1` coerced at 0, → `Idle`) and
|
|
star path (`star()`, `starsCount + 1`, → `Starred`) are both explicit `when` branches, replacing
|
|
the first pass's early-return no-op guard.
|
|
- `RepositoryListViewModelTest.kt`: `repo()` test helper gained a `starsCount` parameter; tests
|
|
cover starring incrementing the count by 1, a repeat tap un-starring (calls both `star()` and
|
|
`unstar()`, decrements the count, returns to `Idle`), and a full star→unstar round trip landing
|
|
back at the original count.
|
|
- `./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` (`adb install -r`, streamed
|
|
install succeeded) twice across this task's two rounds; not yet re-tested live against the final
|
|
toggle behavior in this exact session.
|
|
|
|
## Known limitations
|
|
|
|
- Still optimistic, not authoritative: if Gitea's real count differs from this app's local ±1 for
|
|
any reason (e.g. another client starred/unstarred the same repo around the same time), the
|
|
displayed number stays off until the next full list reload. Considered an acceptable tradeoff
|
|
given the alternative (a second network call per action) for a rarely-contended personal count.
|
|
- The underlying "can't tell if already starred on initial load" limitation (TASK-018/TASK-030)
|
|
is unchanged — a repository starred before the current app session still shows outlined until
|
|
acted on directly in this session, same as before this task.
|
|
- A failed *un*star attempt (network error mid-toggle) still collapses to the same flat `Failed`
|
|
state as a failed star attempt, which renders as outlined/not-starred on the ALL list even though
|
|
the repository may still actually be starred server-side — `FavoriteActionUiState.Failed` carries
|
|
no memory of which action was being attempted. Accepted as a minor, retryable rendering gap
|
|
rather than adding state to track it, since a retry from that visual state is harmless either way
|
|
(an unwanted second `star()` on an already-starred repo is a no-op server-side).
|
|
|
|
## Review findings
|
|
|
|
None yet — pending Codex/human review.
|