Ubisam-MobileGit/tasks/014-organizations.md
choiminyeong f6ac20c8ed TASK-063: task records say what is actually open
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>
2026-09-29 20:25:49 +09:00

72 lines
6.2 KiB
Markdown

# TASK-014: Browse organizations and filter repositories by organization
Status: implemented; all required checks pass; no review pending (Codex retired, 2026-09-29)
Owner: Claude (human product owner directed Claude to own all subsequent tasks, 2026-08-26)
Reviewer: Codex
## Outcome
A signed-in user can see the organizations their account belongs to and filter the repository list by one organization while still being able to return to "all repositories." This closes MVP outcome #2 in `docs/product.md` ("View the signed-in user and organizations").
## Scope
- Included:
- Read-only `GET /user/orgs` and `GET /orgs/{org}/repos`, added to `GiteaRepositoryBrowserApi` as a third small interface, reusing its existing repository-mapping code.
- An organization filter row on the repository list (ALL mode only — favorites are not org-scoped in Gitea), defaulting to "전체" (all), with the existing loading/empty/offline/auth-expired/server-error states applying to the filtered list the same way they already do for the unfiltered one.
- Excluded:
- Any organization administration or membership write (create/join/leave/manage team).
- Filtering the Favorites tab by organization — starred repos are not org-scoped in Gitea's own model.
## Writable paths
- `app/src/main/java/com/ubisam/ubgitea/domain/organizations/`
- `app/src/main/java/com/ubisam/ubgitea/data/repositories/GiteaRepositoryBrowserApi.kt` (additive: new interface implementation only)
- `app/src/test/java/com/ubisam/ubgitea/data/repositories/GiteaRepositoryBrowserApiTest.kt` (additive)
- `app/src/main/java/com/ubisam/ubgitea/feature/repositories/`
- `app/src/test/java/com/ubisam/ubgitea/feature/repositories/`
- `app/src/main/res/values/strings_repositories.xml`
- `tasks/014-organizations.md`
## Acceptance criteria
- [x] Organizations list and organization-scoped repository list are both paginated read-only calls; no membership/team-write endpoint is reachable — `GiteaRepositoryBrowserApi` only adds `get()` calls for these two new methods (grep-confirmed, same as every other read-only endpoint in that class).
- [x] The repository list can be filtered to one organization and back to "all repositories" without losing the recent-push sort or paging behavior already built (TASK-017) — `listOrganizationRepositories` requests the same `sort=updated&order=desc`, uses the same `REPOSITORY_PAGE_SIZE`/page-based navigation, and reuses `mapRepository` so field mapping cannot drift between the two code paths.
- [x] Loading, empty, offline, authentication-expired, and server-error states apply to the organization-filtered list the same way they already do for the unfiltered list — the org-scoped call goes through the exact same `loadRepositoryPage` try/catch and `RepositoryListUiState` cases as the unfiltered call; there is no separate, parallel state machine for it.
- [x] Tests cover organization list mapping, organization-repository mapping (reusing existing repository mapping, not duplicating it), and filter switching — 2 data-layer tests plus 3 ViewModel tests (see Evidence).
## Required checks
```text
./gradlew assembleDebug
./gradlew testDebugUnitTest
./gradlew lintDebug
```
## Evidence
Implemented on branch `claude/task-014-organizations` from commit `7756ec4`.
- Domain: `domain/organizations/OrganizationModels.kt` (`OrganizationSummary(name, displayName)`), `domain/organizations/OrganizationsRepository.kt`. Data: `GiteaRepositoryBrowserApi` gained a third interface implementation (`listOrganizations`, `listOrganizationRepositories`), reusing its existing `mapRepository` for the latter rather than duplicating repository-JSON mapping a third time.
- Verified against Gitea's actual `Organization` Go struct (`name`/`full_name` fields) before implementing, not guessed.
- `RepositoryListViewModel`: `Content`/`Empty` gained `organization: String?` (null = all) and `Content` gained `organizations: List<OrganizationSummary>` for the filter row. The organizations list is fetched once per session (`loadOrganizationsOnce`, cached in `cachedOrganizations`, cleared on logout) rather than on every page load — a failure to load it is treated as non-fatal (empty filter row) and does not block showing the repository list itself, since the list is the primary content and the filter is secondary. `selectOrganization(String?)` only applies in ALL mode (Gitea star is not organization-scoped, so Favorites ignores it, tested explicitly).
- UI: `RepositoryListScreen` gained a horizontally-scrollable `FilterChip` row (전체 + one chip per organization) shown only in ALL mode when at least one organization exists, using each organization's `displayName` when present and falling back to `name`.
Test coverage: 2 new data-layer tests (`organizations map name and full name`, `organization repositories reuse repository mapping and request recent-push order`) plus 3 new ViewModel tests (`selecting an organization filters the list and organizations are exposed for the picker`, `the organizations list is only fetched once and reused across page loads`, `organization filtering does not apply to the favorites tab`).
Checks run from the new worktree (`local.properties` created locally, not committed):
```text
./gradlew testDebugUnitTest --tests "com.ubisam.ubgitea.feature.repositories.RepositoryListViewModelTest" → BUILD SUCCESSFUL
./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks → BUILD SUCCESSFUL
- 169 unit tests across 28 suites, 0 failures/errors
- 0 lint errors
git diff --check → clean (CRLF notice only)
sensitive-pattern scan of changed files → no matches
```
Known limitations:
- No physical device was available to verify the filter chip row or an account with real multiple-organization membership interactively.
- Organization membership itself (which orgs the account belongs to) is read once per session and not refreshed mid-session if the user's org membership changes on the server; a manual logout/login or app restart picks up changes. This matches the app's existing no-background-refresh posture elsewhere.
No open P0-P3 findings from self-review. Requesting Codex read-only review.