Ubisam-MobileGit/tasks/046-commit-history-load-performance.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

109 lines
6.0 KiB
Markdown

# TASK-046: Commit history load performance and loading feedback
Status: implemented; all required checks pass; live-confirmed on the human product owner's device
Owner: Claude
Reviewer: Codex
## Outcome
Live-testing TASK-045's redesign surfaced a real, pre-existing bug: opening the 커밋 (commit history)
tab on a real, large company repository (`RobotAutomationDevelopment-Dept/LGES-OC5-PtoP-Welder`)
consistently failed with "서버에 연결할 수 없습니다" (offline error), even though the device had a
normal network connection throughout. Root-caused, fixed, and confirmed live with the product owner.
## Scope
- Included: `GiteaRepositoryBrowserApi.listCommits` now requests `stat=false`; `HttpTransport`'s
shared OkHttp client's `readTimeout` raised 30s -> 60s as a safety margin; a new
`EstimatedProgressIndicator` (percentage progress bar) replaces the plain spinner on History's
three loading states.
- Excluded: no change to any other API call's stat/file-list behavior (only the commit-list
endpoint was touched); no change to commit detail, which legitimately needs per-file data and
correctly keeps returning it.
## Root cause
Gitea's `GET /repos/{owner}/{repo}/commits` endpoint computes and returns full per-commit file-change
lists and add/remove line stats by default, regardless of what the client asked for. This particular
repository's history contains one commit (a vendored `packages/` folder checked into git) touching
~5.8 million lines. Computing that commit's stats made the *entire request* -- even a 5-item page --
take 31+ seconds server-side, tripping the client's then-30s read timeout. Live-measured directly
against the real endpoint:
```text
default (stat computed): 31.3s, 678653 bytes
stat=false: 0.4s, 678567 bytes
```
Note the response size is nearly identical either way -- the cost is server-side compute time, not
payload size. `GiteaRepositoryBrowserApi.mapCommit` only ever reads `sha`/`commit.message`/
`commit.author.{name,date}` from this response; it never reads `files` or `stats`. `stat=false` is
therefore a pure win: Gitea skips work the app already threw away, with no loss of data the app used.
## Design notes
- **`stat=false` is the real fix; the timeout bump and progress bar are defense in depth**, not the
primary fix -- kept anyway since OkHttp's previous 30s ceiling was already documented (see
`HttpTransport.kt`'s existing comment) as too tight for other genuinely-slow cases against this
same home-NAS-hosted Gitea instance (e.g. very large directory listings), and a percentage
indicator is a real UX improvement for any request that legitimately takes a while.
- **The progress bar is a time-based estimate, not a byte-accurate percentage.** Confirmed via a live
header check that Gitea serves this endpoint `Transfer-Encoding: chunked` with no `Content-Length`,
so there is no real total-bytes figure to compute a true percentage from. `EstimatedProgressIndicator`
ramps linearly toward a 94% cap over an assumed ~20s typical duration and holds there until the real
response arrives (replacing the whole composable with actual content/error) -- it never visually
claims 100% before the data is actually in. This trade-off (an honest estimate, not a precise
measurement) was not explicitly re-confirmed with the product owner after implementing, since the
underlying request became near-instant once `stat=false` was added, making the estimate's accuracy
largely moot in practice.
- **Applied to all three of History's loading states** (commit list, commit detail, patch), not just
the one that was reported, since all three hit the same class of Gitea host and the fix is a
same-shape, low-risk composable swap.
## Acceptance criteria
- [x] `listCommits` requests `stat=false`.
- [x] Live-measured against the real failing repository: request completes in under a second instead
of 30+.
- [x] Loading states show an estimated percentage progress bar instead of a bare spinner.
- [x] No change to what `CommitSummary` exposes or how it's built.
- [x] Build, unit tests (including a new query-parameter assertion), and lint pass.
- [x] Live-confirmed on the human product owner's device, against the exact repository that
originally failed.
## Required checks
```text
./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug
```
## Evidence
- `data/repositories/GiteaRepositoryBrowserApi.kt`: `listCommits` adds `.addQueryParameter("stat", "false")`.
- `core/network/HttpTransport.kt`: `readTimeout` 30s -> 60s, comment extended with this task's
measured numbers.
- `feature/history/HistoryScreen.kt`: new `EstimatedProgressIndicator` + `history_loading_progress`
string, replacing `CircularProgressIndicator()` at all three loading call sites (commit list,
commit detail, patch).
- `GiteaRepositoryBrowserApiTest.kt`: existing `listCommits` test extended to assert
`url.queryParameter("stat") == "false"`.
- Direct `curl` measurement against the live server (see Root cause above): 31.3s -> 0.4s, near-identical
response size either way.
- `./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug`:
BUILD SUCCESSFUL, all unit tests passing, 0 lint errors.
- Live device: installed over wireless `adb`. Human product owner navigated to the previously-failing
repository's commit tab themselves and confirmed it now works well ("내가 확인했어").
## Known limitations
- The progress percentage is a time-based estimate (see Design notes), not derived from real bytes
transferred -- inherent to Gitea serving this endpoint as chunked with no `Content-Length`.
- Only `listCommits` (the list endpoint) was changed. If a future repository has a similarly huge
commit and a user opens that commit's *detail* view, the detail fetch still legitimately needs and
requests full file/stat data, and could be slow for the same underlying reason -- not fixable the
same way, since that view's entire purpose is showing the file changes.
## Review findings
None yet -- no Codex review pending (Codex retired, 2026-09-29).