Ubisam-MobileGit/tasks/014-organizations.md
choiminyeong 4fe3bf1c9f Implement TASK-014: organizations and repository org filter
Adds read-only GET /user/orgs and GET /orgs/{org}/repos to
GiteaRepositoryBrowserApi (a third interface, OrganizationsRepository),
reusing existing repository mapping rather than duplicating it.

Repository list gains an organization filter chip row (all + one per
org), keeping the existing recent-push sort and paging. Organizations
are fetched once per session and cached; a failure to load them
degrades to an empty filter row instead of blocking the repository
list itself. Filtering only applies to the all-repositories mode,
since Gitea star is not organization-scoped.

Closes MVP outcome #2 in docs/product.md. No task file existed
beforehand; created it first per AGENTS.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 15:34:22 +09:00

6.2 KiB

TASK-014: Browse organizations and filter repositories by organization

Status: implemented; all required checks pass; pending Codex review 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.