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

6.2 KiB

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

  • 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).
  • 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.
  • 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.
  • 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

./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):

./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.