# 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 -- pending Codex/human review.