TASK-046: commit history load performance and loading feedback
See tasks/046-commit-history-load-performance.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
71c8457578
commit
5caa6057f1
@ -40,10 +40,19 @@ interface HttpTransport {
|
|||||||
// being misreported as offline, and sharing one connection pool across every feature's API calls
|
// being misreported as offline, and sharing one connection pool across every feature's API calls
|
||||||
// (instead of each of the ~8 call sites opening its own pool) lets repeated calls to the same
|
// (instead of each of the ~8 call sites opening its own pool) lets repeated calls to the same
|
||||||
// Gitea host reuse an already-open TCP/TLS connection rather than renegotiating one per feature.
|
// Gitea host reuse an already-open TCP/TLS connection rather than renegotiating one per feature.
|
||||||
|
//
|
||||||
|
// readTimeout raised again, 30s -> 60s (TASK-045 follow-up): live-reproduced against a real
|
||||||
|
// repository whose history contains a single vendored-dependency commit touching ~5.8 million
|
||||||
|
// lines. Gitea's commit-list endpoint computes per-commit file/stat data server-side regardless of
|
||||||
|
// requested page size, so that one commit's cost is paid on any page containing it -- consistently
|
||||||
|
// took long enough on the Synology NAS host to still trip the previous 30s ceiling. This is a
|
||||||
|
// client-side patience increase, not a fix to Gitea's own response cost; if a host or repository
|
||||||
|
// ever makes even 60s insufficient, the real fix is asking Gitea to omit per-file stats from the
|
||||||
|
// list endpoint (if it supports that), not raising this value indefinitely.
|
||||||
private val sharedOkHttpClient: OkHttpClient by lazy {
|
private val sharedOkHttpClient: OkHttpClient by lazy {
|
||||||
OkHttpClient.Builder()
|
OkHttpClient.Builder()
|
||||||
.connectTimeout(15, TimeUnit.SECONDS)
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
.writeTimeout(15, TimeUnit.SECONDS)
|
.writeTimeout(15, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -243,6 +243,13 @@ class GiteaRepositoryBrowserApi(
|
|||||||
.addQueryParameter("sha", ref.value)
|
.addQueryParameter("sha", ref.value)
|
||||||
.addQueryParameter("page", page.toString())
|
.addQueryParameter("page", page.toString())
|
||||||
.addQueryParameter("limit", pageSize.toString())
|
.addQueryParameter("limit", pageSize.toString())
|
||||||
|
// mapCommit below only ever reads sha/message/author name+date -- never the per-commit
|
||||||
|
// file list or added/removed line stats Gitea computes by default for this endpoint.
|
||||||
|
// That computation is expensive: live-measured at 31s vs 0.4s for the same request
|
||||||
|
// against a real repository with one huge (~5.8M line) commit in its history, entirely
|
||||||
|
// server-side compute time, not payload size (both responses were near-identical size).
|
||||||
|
// stat=false skips work the app already throws away.
|
||||||
|
.addQueryParameter("stat", "false")
|
||||||
path?.takeUnless(RepositoryPath::isRoot)?.let { builder.addQueryParameter("path", it.value) }
|
path?.takeUnless(RepositoryPath::isRoot)?.let { builder.addQueryParameter("path", it.value) }
|
||||||
val response = get(builder.build())
|
val response = get(builder.build())
|
||||||
val items = parseList<CommitDto>(response.body).map(::mapCommit)
|
val items = parseList<CommitDto>(response.body).map(::mapCommit)
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@ -24,8 +25,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
@ -51,6 +52,7 @@ import androidx.compose.ui.text.font.FontFamily
|
|||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import com.ubisam.ubgitea.R
|
import com.ubisam.ubgitea.R
|
||||||
import com.ubisam.ubgitea.domain.history.CommitDetail
|
import com.ubisam.ubgitea.domain.history.CommitDetail
|
||||||
import com.ubisam.ubgitea.domain.history.DiffLine
|
import com.ubisam.ubgitea.domain.history.DiffLine
|
||||||
@ -86,7 +88,7 @@ fun HistoryScreen(
|
|||||||
is HistoryUiState.Loading -> {
|
is HistoryUiState.Loading -> {
|
||||||
BackHandler(onBack = onBack)
|
BackHandler(onBack = onBack)
|
||||||
Header(state.target)
|
Header(state.target)
|
||||||
CenterMessage { CircularProgressIndicator() }
|
CenterMessage { EstimatedProgressIndicator() }
|
||||||
}
|
}
|
||||||
is HistoryUiState.CommitList -> {
|
is HistoryUiState.CommitList -> {
|
||||||
BackHandler(onBack = onBack)
|
BackHandler(onBack = onBack)
|
||||||
@ -105,7 +107,7 @@ fun HistoryScreen(
|
|||||||
is HistoryUiState.CommitDetailLoading -> {
|
is HistoryUiState.CommitDetailLoading -> {
|
||||||
BackHandler(onBack = onBackToList)
|
BackHandler(onBack = onBackToList)
|
||||||
Header(state.target)
|
Header(state.target)
|
||||||
CenterMessage { CircularProgressIndicator() }
|
CenterMessage { EstimatedProgressIndicator() }
|
||||||
}
|
}
|
||||||
is HistoryUiState.CommitDetailContent -> {
|
is HistoryUiState.CommitDetailContent -> {
|
||||||
CommitDetailScreen(state, onRetryPatch, onFileSelected, onClearFileSelection, onBackToList)
|
CommitDetailScreen(state, onRetryPatch, onFileSelected, onClearFileSelection, onBackToList)
|
||||||
@ -386,7 +388,7 @@ private val DiffAddedColor = Color(0xFF2E7D32)
|
|||||||
@Composable
|
@Composable
|
||||||
private fun PatchSection(patch: PatchUiState, onRetry: () -> Unit) {
|
private fun PatchSection(patch: PatchUiState, onRetry: () -> Unit) {
|
||||||
when (patch) {
|
when (patch) {
|
||||||
PatchUiState.Loading -> CenterMessage { CircularProgressIndicator() }
|
PatchUiState.Loading -> CenterMessage { EstimatedProgressIndicator() }
|
||||||
is PatchUiState.Loaded -> PatchText(patch.text, patch.isTruncated)
|
is PatchUiState.Loaded -> PatchText(patch.text, patch.isTruncated)
|
||||||
PatchUiState.Failed -> CenterMessage {
|
PatchUiState.Failed -> CenterMessage {
|
||||||
Text(stringResource(R.string.history_patch_failed))
|
Text(stringResource(R.string.history_patch_failed))
|
||||||
@ -433,6 +435,34 @@ private fun CenterMessage(content: @Composable ColumnScope.() -> Unit) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gitea's commit-list response is chunked (no Content-Length -- confirmed against the live server),
|
||||||
|
// so there is no real byte count to report a true download percentage from. This instead estimates
|
||||||
|
// progress from elapsed time against a typical-case duration, capped short of 100% so a request that
|
||||||
|
// runs long never visually lies about being finished -- it just holds near the cap until the actual
|
||||||
|
// response arrives and this composable is replaced by the real content/error state. Purely
|
||||||
|
// decorative/local state: nothing here is persisted or read by the ViewModel.
|
||||||
|
private const val ESTIMATED_LOAD_DURATION_MILLIS = 20_000L
|
||||||
|
private const val ESTIMATED_LOAD_PROGRESS_CAP = 0.94f
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EstimatedProgressIndicator() {
|
||||||
|
var progress by remember { mutableFloatStateOf(0f) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
while (progress < ESTIMATED_LOAD_PROGRESS_CAP) {
|
||||||
|
val elapsed = System.currentTimeMillis() - start
|
||||||
|
progress = (elapsed.toFloat() / ESTIMATED_LOAD_DURATION_MILLIS).coerceAtMost(ESTIMATED_LOAD_PROGRESS_CAP)
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LinearProgressIndicator(progress = { progress }, modifier = Modifier.width(160.dp))
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.history_loading_progress, (progress * 100).toInt()),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val FileChangeStatus.labelResource: Int
|
private val FileChangeStatus.labelResource: Int
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
FileChangeStatus.ADDED -> R.string.history_status_added
|
FileChangeStatus.ADDED -> R.string.history_status_added
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
<string name="history_status_renamed">이름 변경됨</string>
|
<string name="history_status_renamed">이름 변경됨</string>
|
||||||
<string name="history_status_other">기타 변경</string>
|
<string name="history_status_other">기타 변경</string>
|
||||||
<string name="history_offline">서버에 연결할 수 없습니다. 네트워크를 확인해 주세요.</string>
|
<string name="history_offline">서버에 연결할 수 없습니다. 네트워크를 확인해 주세요.</string>
|
||||||
|
<string name="history_loading_progress">불러오는 중… %1$d%%</string>
|
||||||
<string name="history_auth_expired">로그인이 만료되었습니다. 다시 로그인해 주세요.</string>
|
<string name="history_auth_expired">로그인이 만료되었습니다. 다시 로그인해 주세요.</string>
|
||||||
<string name="history_permission_denied">이 저장소의 커밋 내역을 볼 권한이 없습니다.</string>
|
<string name="history_permission_denied">이 저장소의 커밋 내역을 볼 권한이 없습니다.</string>
|
||||||
<string name="history_not_found">커밋이나 기준을 찾을 수 없습니다.</string>
|
<string name="history_not_found">커밋이나 기준을 찾을 수 없습니다.</string>
|
||||||
|
|||||||
@ -274,6 +274,7 @@ class GiteaRepositoryBrowserApiTest {
|
|||||||
val url = transport.urls.single().toHttpUrl()
|
val url = transport.urls.single().toHttpUrl()
|
||||||
assertEquals("refs/heads/a+b", url.queryParameter("sha"))
|
assertEquals("refs/heads/a+b", url.queryParameter("sha"))
|
||||||
assertEquals("src/a b.kt", url.queryParameter("path"))
|
assertEquals("src/a b.kt", url.queryParameter("path"))
|
||||||
|
assertEquals("false", url.queryParameter("stat"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -1060,3 +1060,49 @@ This is the append-only implementation and handoff record shared by Codex and Cl
|
|||||||
- Next owner: Claude, continuing per the standing direction; human product owner for the device
|
- Next owner: Claude, continuing per the standing direction; human product owner for the device
|
||||||
verification and the logo-asset decision.
|
verification and the logo-asset decision.
|
||||||
|
|
||||||
|
## 2026-09-08 (continued) — TASK-046: commit history load performance and loading feedback
|
||||||
|
|
||||||
|
- Agent and role: Claude, incident responder -- found live while the product owner was testing
|
||||||
|
TASK-045's redesign on-device (wireless adb eventually reconnected after several failed attempts;
|
||||||
|
see TASK-045's own known limitations for that saga).
|
||||||
|
- Commit/diff: same working tree as TASK-045 (main checkout, branch `claude/task-015-notifications-main`),
|
||||||
|
not yet committed; new `tasks/046-commit-history-load-performance.md`.
|
||||||
|
- Root cause and fix: the product owner reported the 커밋 (commit history) tab failing with
|
||||||
|
"서버에 연결할 수 없습니다" on a real, large repository. Root-caused via live screenshots (adb
|
||||||
|
screencap, requiring per-display capture on the product owner's foldable -- the default display
|
||||||
|
argument returns corrupted output on multi-display devices) plus direct `curl` timing against the
|
||||||
|
live Gitea server: the failing endpoint (`GET .../commits`) computes full per-commit file/stat data
|
||||||
|
server-side by default, and this repository's history contains one commit touching ~5.8 million
|
||||||
|
lines (a vendored `packages/` folder committed to git), making even a 5-item page take 31+ seconds --
|
||||||
|
tripping the client's 30s read timeout. `stat=false` skips that computation; measured 31.3s -> 0.4s
|
||||||
|
for the identical request, with near-identical response size either way (confirming the cost was
|
||||||
|
compute, not payload). `GiteaRepositoryBrowserApi.listCommits` never reads the `files`/`stats`
|
||||||
|
fields it was implicitly paying for, so this is a pure win.
|
||||||
|
- How it was implemented: `listCommits` now sends `stat=false`. `HttpTransport`'s shared OkHttp
|
||||||
|
client's `readTimeout` raised 30s -> 60s as defense in depth (this exact class of "slow-but-working
|
||||||
|
request misreported as offline" was already a documented, previously-fixed issue in that file's own
|
||||||
|
comment -- this is its second escalation). A new `EstimatedProgressIndicator` composable in
|
||||||
|
`HistoryScreen.kt` replaces the plain spinner across all three of History's loading states with a
|
||||||
|
percentage progress bar; confirmed via a live response-header check that Gitea serves this endpoint
|
||||||
|
chunked with no `Content-Length`, so the percentage is a time-based estimate (ramped toward a 94%
|
||||||
|
cap, never claiming 100% before real data arrives), not a true byte count -- disclosed to the
|
||||||
|
product owner as such.
|
||||||
|
- Files changed: `data/repositories/GiteaRepositoryBrowserApi.kt`, `core/network/HttpTransport.kt`,
|
||||||
|
`feature/history/HistoryScreen.kt`, `res/values/strings_history.xml`,
|
||||||
|
`GiteaRepositoryBrowserApiTest.kt`, `tasks/046-commit-history-load-performance.md`.
|
||||||
|
- Checks and results: `./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug`
|
||||||
|
BUILD SUCCESSFUL, all unit tests passing (including a new assertion that `listCommits` sends
|
||||||
|
`stat=false`), 0 lint errors.
|
||||||
|
- Live verification: installed over wireless `adb` (after a lengthy reconnection struggle -- the
|
||||||
|
connection kept dropping when the product owner's phone screen turned off, requiring several fresh
|
||||||
|
pairing codes). Product owner navigated to the exact repository/commit tab that had failed before
|
||||||
|
and confirmed it now works well themselves ("내가 확인했어").
|
||||||
|
- Review findings and resolutions: None from a separate review pass yet.
|
||||||
|
- Known limitations: the progress percentage is a time estimate, not real byte progress (inherent to
|
||||||
|
the endpoint's chunked response, see task file). Commit *detail* view for an equally huge commit
|
||||||
|
would still legitimately be slow, since showing file changes is that view's entire purpose --
|
||||||
|
not addressed by this fix, and not fixable the same way.
|
||||||
|
- Next recommended task: none queued from this incident. TASK-045's own remaining items (device
|
||||||
|
confirmation of the visual redesign specifically, and the real logo asset) are still open.
|
||||||
|
- Next owner: Claude, continuing per the standing direction.
|
||||||
|
|
||||||
|
|||||||
108
tasks/046-commit-history-load-performance.md
Normal file
108
tasks/046-commit-history-load-performance.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# 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.
|
||||||
Loading…
x
Reference in New Issue
Block a user