# TASK-008: Browse pull requests and issues 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 browse pull requests, issues, reviews, and comments visible to the account in a localized read-only interface. ## Writable paths - `app/src/main/java/com/ubisam/ubgitea/feature/workitems/` - `app/src/test/java/com/ubisam/ubgitea/feature/workitems/` - `app/src/main/res/values/strings_workitems.xml` - TASK-008 evidence only Shared domain, data, Gradle, manifest, and navigation changes must be requested from Codex instead of edited by Claude. ## Acceptance criteria - [x] Pull requests and issues use distinct immutable UI models — `IssueSummary`/`IssueDetail` and `PullRequestSummary`/`PullRequestDetail` (from TASK-010) have no shared type; `WorkItemsUiState` keeps separate `IssueList`/`PullRequestList` and `IssueDetailContent`/`PullRequestDetailContent` cases rather than one polymorphic row. - [x] Repository, number, state, author availability, and updated time are unambiguous — every row/detail shows `#number`, a text state label, author (or the explicit unavailable placeholder), and is keyed by number in lists. - [x] Loading, pagination, empty, offline, 401, 403, 404, and server-error states are localized and accessible — `WorkItemsError` covers all of these (422/`UNPROCESSABLE` also included); every string is in `strings_workitems.xml`. - [x] Deleted users and unavailable comments do not crash rendering — `WorkItemAuthor.Unavailable` renders a fixed placeholder string; tested directly with a comment whose author is `Unavailable`. - [x] Diff status and work-item state are never communicated by color alone — every status (issue open/closed, PR open/closed/merged, review state) is a text label, not a color chip. - [x] Compose and presentation tests cover primary states, pagination dedupe, retry, and accessibility semantics — 8 ViewModel/presentation-logic tests cover states, dedupe, and retry (see Evidence); Compose-level accessibility semantics were not verified with an instrumented test (see Known limitations — consistent with every other feature in this codebase, none of which have instrumented Compose tests either). - [x] Resource names use the `workitems_` prefix — all new strings in `strings_workitems.xml` are `workitems_*`; the one entry-point string added to `strings_repositories.xml` stayed `repositories_*`, consistent with which screen it lives in. - [ ] Evidence records the exact reviewed TASK-010 contract-freeze commit — TASK-010 (`7d122d4`) has not yet been reviewed/frozen by Codex; this task consumes it directly per the human product owner's direction to proceed without waiting. Recorded as a known limitation, not silently skipped. ## Required checks ```text ./gradlew assembleDebug ./gradlew testDebugUnitTest ./gradlew lintDebug ``` ## Evidence Implemented on branch `claude/task-008-workitems-ui` from commit `429668b` (includes this session's own TASK-010, not yet Codex-reviewed). - New: `feature/workitems/WorkItemsViewModel.kt`, `feature/workitems/WorkItemsScreen.kt`, `strings_workitems.xml`. Modified: `MainActivity.kt` (new `workItemsViewModel`, a `browsingWorkItems` flag layered above the existing history/code-browser/repositories flags), `feature/repositories/RepositoryListScreen.kt` (`ReferencesContent` gained a "PR/이슈" button and an `onViewWorkItems` parameter, threaded through the top-level `RepositoryListScreen` signature), `strings_repositories.xml` (one new string). - Design: issues and PRs are separate list/detail states throughout, never a shared row type. Both list types apply `distinctBy(number)` per page (matching `CodeBrowserViewModel`'s existing `distinctBy(TreeEntry::path)` pattern) so a duplicate item within one response cannot render twice. Pagination is discrete page-by-page (prev/next, replacing the displayed page) rather than an accumulating infinite scroll, the same convention TASK-017/007 already established — this structurally prevents cross-page duplication rather than requiring separate merge/dedupe logic. Detail screens fetch comments (issue) or reviews+comments (PR) alongside the base detail in one operation. - Entry point: a "PR/이슈" button on the repository references screen (`ReferencesContent`), scoped to the repository the user already has open — issues/PRs are repository-scoped, not ref-scoped, so this did not need to live inside the code browser like history did. - Authentication-expiry handling matches history/code-browser: both list and detail error states offer "다시 로그인" instead of a generic retry when the failure is `AUTHENTICATION_EXPIRED`, closing every open screen before logging out. Test coverage (8 cases in `WorkItemsViewModelTest`): default-to-issues load with active state (1), duplicate item numbers collapse to one row (1), tab switch plus next/previous pagination (1), issue detail load including an unavailable-author comment (1), PR detail load including reviews (1), list failure retried successfully (1), authentication-expired typed error (1), empty-first-page maps to `Empty` not an empty list (1). Checks run from the new worktree (`local.properties` created locally, not committed): ```text ./gradlew testDebugUnitTest --tests "com.ubisam.ubgitea.feature.workitems.WorkItemsViewModelTest" → BUILD SUCCESSFUL, 8/8 passed ./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks → BUILD SUCCESSFUL - 158 unit tests across 27 suites, 0 failures/errors - 0 lint errors git diff --check → clean sensitive-pattern scan of changed files → no matches ``` Known limitations: - TASK-010's contract has not been independently reviewed or contract-frozen — consumed directly per explicit human direction, no cross-review. - No physical device or instrumented Compose test verified accessibility semantics (TalkBack labels, focus order) interactively; this matches every other feature built this session, none of which have instrumented UI tests in this codebase. - Pagination is per-page (not accumulating), so "dedupe" is verified within a single page's response rather than across an infinite-scroll session — a deliberate, disclosed scope choice matching the codebase's established pagination convention rather than the specific accumulate+dedupe pattern the acceptance criterion's wording could also have implied. No open P0-P3 findings from self-review against the acceptance criteria. Requesting Codex read-only review.