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>
88 lines
7.6 KiB
Markdown
88 lines
7.6 KiB
Markdown
# TASK-017: Sort repositories by recent push, page the list, and show the last-pushed branch
|
|
|
|
Status: implemented; all required checks pass; no review pending (Codex retired, 2026-09-29)
|
|
Owner: Claude (human product owner priority-1 request, 2026-08-26)
|
|
Reviewer: Codex
|
|
|
|
## Outcome
|
|
|
|
The repository list shows the signed-in account's repositories most-recently-pushed first, loads them a page at a time instead of eagerly loading everything, and shows each visible repository's most recently pushed branch.
|
|
|
|
## Scope
|
|
|
|
- Included:
|
|
- Add `updatedAt` (Gitea `updated_at`) to `RepositorySummary` and request server-side `sort=updated&order=desc` so ordering is correct across pages.
|
|
- Replace the legacy eager-loading `RepositoryListApi`/`AccessibleRepositoriesGateway` (which pages internally and returns one aggregated list) with the existing paged `GiteaRepositoryBrowserApi.listRepositories(page, pageSize)` from TASK-004, wired through the domain `Page<RepositorySummary>` contract.
|
|
- Reduce the repository list page size to 5 (human product owner decision, 2026-08-26) with explicit load-more/pagination UI, replacing today's single unpaginated load.
|
|
- Add `commit.timestamp` to `BranchSummary` (mapped from Gitea's branch-list `commit.timestamp` field) and, for each repository visible on the current page only, fetch its branches and surface the one with the latest `commit.timestamp` as "last pushed branch."
|
|
- Excluded:
|
|
- Any write/mutation of repository or branch state.
|
|
- Favorites/starring (TASK-018).
|
|
- Sorting or paging the branch/tag list itself (already paged; unaffected).
|
|
|
|
## Writable paths
|
|
|
|
- `app/src/main/java/com/ubisam/ubgitea/domain/model/RepositoryModels.kt` (add fields only; no existing field removed)
|
|
- `app/src/main/java/com/ubisam/ubgitea/data/repositories/`
|
|
- `app/src/main/java/com/ubisam/ubgitea/feature/repositories/`
|
|
- `app/src/test/java/com/ubisam/ubgitea/domain/model/`
|
|
- `app/src/test/java/com/ubisam/ubgitea/data/repositories/`
|
|
- `app/src/test/java/com/ubisam/ubgitea/feature/repositories/`
|
|
- `app/src/main/res/values/strings_repositories.xml`
|
|
- `tasks/017-repository-list-recent-push.md`
|
|
- `docs/development-log.md` (append-only)
|
|
|
|
## Acceptance criteria
|
|
|
|
- [x] Repository list requests `sort=updated&order=desc`; ordering is correct across page boundaries (verified by a fake-transport test asserting the query parameters, not by trusting client-side re-sorting).
|
|
- [x] `RepositorySummary.updatedAt` is populated and covered by DTO-mapping tests (present, absent/malformed → typed failure, consistent with existing `.required()` fail-closed pattern).
|
|
- [x] The repository list screen loads 5 repositories per page with explicit next-page loading; no behavior change for accounts with 5 or fewer repositories.
|
|
- [x] The legacy `RepositoryListApi`/`AccessibleRepositoriesGateway` code path and its now-unused tests are removed once the ViewModel is migrated to `GiteaRepositoryBrowserApi.listRepositories`; no duplicate repository-list implementation remains.
|
|
- [x] Each of the 5 visible repository rows independently fetches and displays its last-pushed branch name once available; a slow or failed per-row branch fetch does not block or fail the rest of the page, and is retried independently.
|
|
- [x] Repository row branch-timestamp fetches are cancelled when their row scrolls out of the current page/is replaced by a refresh, to avoid orphaned requests (races/cancellation, matching the existing `startOperation` pattern).
|
|
- [x] Loading, empty, offline, authentication-expired, and server-error states remain accessible and localized for both the repository page and the per-row branch fetch.
|
|
- [x] Tests cover: sort/pagination query construction, page-boundary correctness, per-row branch-timestamp selection (including a repository with zero branches, and a tie on identical timestamps), and cancellation on row replacement.
|
|
|
|
## Security and privacy
|
|
|
|
No additional concerns — read-only, no new OAuth scope, no credential or token exposure.
|
|
|
|
## Required checks
|
|
|
|
```text
|
|
./gradlew assembleDebug
|
|
./gradlew testDebugUnitTest
|
|
./gradlew lintDebug
|
|
```
|
|
|
|
## Evidence
|
|
|
|
Implemented on branch `claude/task-017-recent-push-favorites` from commit `fdda164` (TASK-004/005/006 reviewed-and-frozen base).
|
|
|
|
- Domain: `RepositorySummary` gained `updatedAtEpochMillis: Long`; `defaultBranch` changed from non-null `RepositoryRef` to nullable `RepositoryRef?` (an empty/newly-created Gitea repository can have no default branch — previously this threw `ApiFailure.InvalidResponse()` for the *entire* page instead of marking just that one repository, a latent bug surfaced by routing the main list through the already-existing TASK-004 paged endpoint). `BranchSummary` gained `commitAtEpochMillis: Long`.
|
|
- Data: `GiteaRepositoryBrowserApi.listRepositories` now requests `sort=updated&order=desc` and maps `updated_at`; `listBranches` maps `commit.timestamp`. A shared `String.toEpochMillisOrInvalid()` helper replaced three near-duplicate `Instant.parse`/catch blocks (also used by `mapCommit`, no behavior change there).
|
|
- Removed `RepositoryListApi.kt`/`AccessibleRepositoriesGateway`/`AccessibleRepository` and their test file — this was a second, independent repository-list implementation with its own internal eager-all-pages loop; the ViewModel now uses only `GiteaRepositoryBrowser.listRepositories`.
|
|
- `RepositoryListViewModel`: repository loading is now page-based (`REPOSITORY_PAGE_SIZE = 5`) with `nextPage()`/`previousPage()`/`refreshRepositories()`. Each visible row launches its own cancellable branch-fetch job (tracked in `rowBranchJobs`, keyed by `RepositoryCoordinates`) that pages through that repository's branches and picks the maximum `commitAtEpochMillis`; `Content` rows carry a `LastPushedBranchUiState` (`Loading`/`Loaded`/`Empty`/`Failed`). Row jobs are cancelled and relaunched on every page load/refresh (`cancelRowBranchJobs()` at the top of `loadRepositoryPage`) and cancelled on navigating away to references or logging out; `backToRepositories()` resumes only rows still `Loading` rather than restarting completed ones. A row's `AuthenticationExpired` failure clears the session the same way every other authenticated call does, instead of being swallowed as a per-row `Failed` state. `retryRowLastPushedBranch` retries a single row without touching the rest of the page.
|
|
- UI: `RepositoryListScreen`'s repository list gained a refresh button, a per-row last-pushed-branch line (with localized loading/empty/failed+retry states), and a previous/page-number/next footer row, all localized (`strings_repositories.xml`).
|
|
- `MainActivity` passes `repository.coordinates` directly instead of re-validating owner/name through `RepositoryCoordinates.of` a second time.
|
|
|
|
Checks (JDK 21 via `D:\AndroidStudio\jbr`, Android SDK at `D:\Android\WhatToEat\sdk`, `local.properties` created locally, not committed):
|
|
|
|
```text
|
|
./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks → BUILD SUCCESSFUL
|
|
- 105 unit tests across 22 suites, 0 failures/errors
|
|
- 0 lint errors
|
|
git diff --check → only CRLF-normalization notices, no real whitespace errors
|
|
sensitive-pattern scan of changed files → no matches
|
|
```
|
|
|
|
Known limitations:
|
|
- No physical device was available; page navigation, per-row branch loading, and the removed legacy path have not been manually verified on-device.
|
|
- Tie-breaking when two branches share the exact same commit timestamp is deterministic (`maxByOrNull` keeps the first match) but arbitrary from a product standpoint; acceptable since this is a rare edge case and not user-configurable.
|
|
|
|
No open P0-P3 findings from self-review against the acceptance criteria. Requesting Codex read-only review.
|
|
|
|
## Review findings
|
|
|
|
None yet.
|