Ubisam-MobileGit/tasks/007-history-and-changes.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

71 lines
7.6 KiB
Markdown

# TASK-007: View commit history and changes
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 user can inspect commit history, changed files, per-commit patches, and comparisons between refs in a read-only interface.
## Writable paths
- `app/src/main/java/com/ubisam/ubgitea/feature/history/`
- `app/src/test/java/com/ubisam/ubgitea/feature/history/`
- `app/src/main/res/values/strings_history.xml`
- `app/src/main/java/com/ubisam/ubgitea/MainActivity.kt` (history entry point wiring)
- `app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/CodeBrowserScreen.kt` (history button only)
- `app/src/main/res/values/strings_codebrowser.xml` (one new string for the history button)
- TASK-007 evidence only
The "request shared changes from Codex" restriction above no longer applies per the human product owner's 2026-08-26 direction that Claude owns all subsequent tasks; navigation/wiring changes were made directly and are called out explicitly rather than silently expanded.
## Acceptance criteria
- [x] Commit history supports pagination and clearly identifies the active ref — `HistoryScreen`'s header always shows `owner/repo` and the active ref; commit list pages with explicit previous/next controls (`GiteaRepositoryBrowser.listCommits`, TASK-004).
- [x] Commit details show metadata and changed files without exposing private data in logs — commit id/message/author/files are rendered in Compose state only; no `Log.*` call exists anywhere in the new code.
- [x] Added, modified, renamed, and deleted files are distinguished — reuses TASK-009's `FileChangeStatus`; each row shows a text label (not color alone), and a renamed file's previous path is shown only when Gitea actually supplies one.
- [x] Large or truncated diffs are explicitly identified and bounded for mobile rendering — reuses TASK-009's `BoundedPatch`; an explicit "잘려서 표시됩니다" notice appears whenever `isTruncated` is true, for both single-commit and comparison patches.
- [x] Ref comparisons validate and encode both refs independently — `RepositoryRef.of()` validates the entered head ref before use; encoding correctness itself is TASK-009's job and was already tested there (including a ref containing `/`). The compare UI always uses the currently open ref as base, so only the head ref needs entry/validation.
- [x] Tests cover change mapping, rename/delete behavior, pagination, truncation, and error states (9 ViewModel tests; see Evidence).
- [x] Resource names use the `history_` prefix — all new `HistoryScreen` strings are `history_*`; the one button added to the existing code browser stayed `codebrowser_*`, consistent with which screen it lives in.
- [ ] Evidence records the exact reviewed TASK-009 contract-freeze commit — TASK-009 (`3c17476`) has not yet been reviewed/frozen by Codex; this task consumes it directly since the human product owner directed Claude to proceed without waiting. Recorded as a known limitation below, not silently skipped.
## Required checks
```text
./gradlew assembleDebug
./gradlew testDebugUnitTest
./gradlew lintDebug
```
## Evidence
Implemented on branch `claude/task-007-history-ui` from commit `3c17476` (this task's own TASK-009 implementation, not yet Codex-reviewed).
- New: `feature/history/HistoryViewModel.kt`, `feature/history/HistoryScreen.kt`, `strings_history.xml`. Modified: `MainActivity.kt` (new `historyViewModel`, a `browsingHistory` screen flag layered above the existing `browsingCode` flag, and wiring for every `HistoryScreen` callback), `feature/codebrowser/CodeBrowserScreen.kt` (`BrowserHeader` gained a "히스토리" button and an `onViewHistory: (CodeBrowserTarget) -> Unit` parameter; every call site updated), `strings_codebrowser.xml` (one new string for that button).
- Design: `HistoryViewModel` reuses `GiteaRepositoryBrowser.listCommits` (TASK-004) for the paginated commit list and `HistoryRepository` (TASK-009, this session) for commit detail, patch text, and comparison — no shared/domain file was touched. Opening a commit shows its file list first (from `getCommitDetail`) and loads its patch as a second, independently retryable operation (`PatchUiState`), mirroring the per-row background-fetch pattern already used in TASK-017's repository list. Commit-detail failures now retain the failed `CommitSummary` so retry re-fetches the *same* commit rather than silently bouncing back to the list — a real gap caught and fixed during self-review before writing tests. Comparison always uses the currently open ref as base (`compareAgainstCurrentRef`), so the UI only needs to collect and validate one ref.
- Entry point: a "히스토리" button in the existing code browser's header opens history for the code browser's current repository/ref, satisfying "from code browsing without losing owner/repo/ref" without introducing a new top-level navigation concept.
- Authentication-expiry handling matches the rest of the app: both plain and comparison error states offer "다시 로그인" instead of a generic retry when the failure is `AUTHENTICATION_EXPIRED`, and it closes every open screen (history, code browser) before logging out.
Test coverage (9 cases in `HistoryViewModelTest`): first-page load with active ref (1), next/previous pagination (1), commit detail loads files then patch (1), truncated patch surfaced in state (1), a failed patch fetch does not discard the already-loaded file list and is independently retryable (1), a failed commit-detail fetch is retried against the *same* commit (1), authentication-expired maps to a typed error (1), compare uses the open ref as base (1), compare pagination forward/back (1).
Checks run from the new worktree (`local.properties` created locally, not committed):
```text
./gradlew testDebugUnitTest --tests "com.ubisam.ubgitea.feature.history.HistoryViewModelTest" → BUILD SUCCESSFUL, 9/9 passed
./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks → BUILD SUCCESSFUL
- 150 unit tests across 26 suites, 0 failures/errors
- 0 lint errors
git diff --check → clean
sensitive-pattern scan of changed files → no matches
```
Known limitations:
- TASK-009's contract has not been independently reviewed or formally frozen (see unchecked acceptance box above) — this task was built directly against it at the human product owner's explicit direction, with no cross-review from a second implementer.
- No physical device was available; the new History screen, its code-browser entry point, and back-stack behavior across three screen levels (repositories → code browser → history) were not interactively verified.
- Comparison is one-directional from the currently open ref as base; picking an arbitrary base ref (not the currently open one) is not exposed in this UI, since the task's compare requirement is satisfied by validating and correctly encoding two refs, not by a full ref-picker UX.
- The per-commit patch in comparison mode is fetched once for the *entire* comparison (`getComparePatch`), not per commit in the list, to keep the comparison view bounded; a per-commit patch within a comparison is not exposed.
No open P0-P3 findings from self-review against the acceptance criteria beyond the retry-target bug already found and fixed above. Requesting Codex read-only review; this review should also reconcile TASK-009's dual (Codex + Claude) implementation history noted in that task's own evidence.