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

7.6 KiB

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

  • 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).
  • 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.
  • 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.
  • 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.
  • 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.
  • Tests cover change mapping, rename/delete behavior, pagination, truncation, and error states (9 ViewModel tests; see Evidence).
  • 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

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

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