- '히스토리' label -> '커밋' across the branch-detail tab, code-browser history button, and the one permission-denied sentence that used the word, matching what that screen actually shows. - SourceContentClassifier now tries EUC-KR (strictly, same discipline as its existing UTF-8 attempt) before giving up, so legacy Korean-encoded .cpp/.h source files render instead of failing as UnsupportedEncoding. A byte sequence invalid in both charsets still correctly falls through. - OkHttpTransport's default client is now one shared, longer-timeout OkHttpClient (15s/30s/15s) instead of a fresh unconfigured OkHttpClient() per call site -- fixes a large commit history/directory listing on this app's home-NAS-hosted Gitea being misreported as offline purely because it took longer than the previous 10s default to answer, and lets calls to the same host reuse one connection pool instead of ~8 separate ones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
10 KiB
TASK-031: "커밋" label rename, EUC-KR decode fallback, HTTP timeout/connection-pooling fix
Status: implemented; all required checks pass; live-installed, awaiting the human product owner's confirmation Owner: Claude Reviewer: Codex
Outcome
Three small, independent fixes reported live in the same testing session: the "히스토리" (History)
tab/button label is now "커밋" (Commit), matching the human product owner's own correct
description of what that screen actually shows; .cpp/.h (and any other legacy-encoded) source
files that use EUC-KR instead of UTF-8 — common in older or Windows-authored Korean C/C++
codebases — now render instead of being rejected as UnsupportedEncoding; and a large repository's
commit history (and, more generally, any slow-but-working request against this app's home-NAS-hosted
Gitea instance) no longer gets misreported as "서버에 연결할 수 없습니다. 네트워크를 확인해
주세요." (can't reach the server) purely because it took longer than the previous 10-second default
timeout to answer.
Scope
- Included:
app/src/main/res/values/strings_branchdetail.xml,strings_codebrowser.xml,strings_history.xml(label/wording changes only);feature/codebrowser/content/SourceContentClassifier.kt(+ matching test) for the EUC-KR fallback;core/network/HttpTransport.ktfor the shared, longer-timeoutOkHttpClient. - Excluded: No change to the actual commit-history or file-browsing functionality themselves — this batch is a label, a decode fallback, and a network-client tuning change, not new features. No attempt to distinguish "genuinely offline" from "slow but reachable" in the error message itself (see Known limitations) — the fix is to stop timing out prematurely, not to redesign the error taxonomy.
Design notes
- Label rename: the human product owner directly confirmed, then asked for, this: the
"History" tab (branch detail) and "History" button (code browser, jumps to a file's commit
history) both said "히스토리"; both are now "커밋" to match what a Gitea user actually expects
that label to mean.
history_permission_denied's sentence ("이 저장소의 히스토리를 볼 권한이 없습니다") was reworded to "커밋 내역을 볼 권한이 없습니다" for the same reason — it's the only other user-facing string in the app that used the word "히스토리" on its own (a full res/values scan confirmed these were the only three occurrences). Internal identifiers (HistoryScreen,HistoryViewModel,history_*resource-name prefixes, thefeature/historypackage) were left alone — renaming those is a much larger, purely-internal refactor with no user-visible benefit, and touching them risked the exact kind of unrelated churn this session's standing "don't change functionality" constraint warns against. - EUC-KR fallback, not a blind fallback: the classifier's own design principle — decode with
CodingErrorAction.REPORT, neverREPLACE, so a byte sequence that isn't cleanly one specific encoding is refused rather than rendered as mojibake — is preserved exactly; a second charset is now tried with the same strict decode, not a permissive one. EUC-KR specifically (not the more complete MS949/CP949 superset some of the same files may technically use) because Android's ownCharsetdocumentation lists EUC-KR as present on every device via bundled ICU data, while CP949/MS949 support is not guaranteed — using it would have risked compiling and passing this project's host-JVM unit tests while throwingUnsupportedCharsetExceptionat runtime on the actual device, which is exactly the kind of gap this project's tests wouldn't have caught without a live device check. A byte sequence invalid in both UTF-8 and EUC-KR (e.g.0xFFas a lead byte, valid in neither) still correctly falls through toUnsupportedEncoding— this is not a catch-all "always render something" fallback, it's specifically shaped around the one legacy encoding this app's real, live-tested repositories actually turned out to use. - HTTP timeouts and connection sharing, one root cause for two symptoms: every one of the
app's ~8
OkHttpTransport()call sites (one per feature'sViewModel.Factory, plusAuthSession) constructed its own fresh, default-configuredOkHttpClient()— default 10-second connect/read/ write timeouts. Traced directly from the live bug report (commit listing on a real, larger-history repository — visible on device as "LGD-P9-CLCI-4K" — reliably produced the generic offline error) throughGiteaRepositoryBrowserApi.authenticated/GiteaHistoryApi.get, both of which catchIOExceptionbroadly (this necessarily includesSocketTimeoutException) and wrap it asApiFailure.Network, which every feature's error mapping renders identically to a truly offline device. This app's actual Gitea host is a home Synology NAS, not a datacenter service, so a legitimately slow-but-working response — a large commit history walk, a directory listing with many entries (the second, related complaint: loading feeling too slow as file counts grow) — was being cut off and misreported before it had a real chance to finish. One sharedOkHttpClient(15s connect / 30s read / 15s write, versus the previous unconfigured 10s/10s/10s) is nowOkHttpTransport's default, replacing the previousOkHttpClient()bare default: longer timeouts give a slow response room to actually complete instead of erroring out early, and because it's now one shared instance (not ~8 independently-constructed ones), OkHttp's own connection pool is reused across every feature's calls to the same host within a session — a second call to the same Gitea host during the same app session can reuse an already-open TCP/TLS connection instead of paying a fresh handshake, which is a genuine (not just perceived) latency win on top of the timeout fix.
Acceptance criteria
- The branch-detail tab and the code-browser's "view this file's history" button both read "커밋" instead of "히스토리".
history_permission_denied's message no longer contains the word "히스토리".- A
.cpp/.h(or any text) file encoded in EUC-KR, not UTF-8, renders as text instead ofUnsupportedEncoding. - A byte sequence invalid in both UTF-8 and EUC-KR still correctly classifies as
UnsupportedEncoding— the fallback doesn't swallow genuinely unrecognized encodings. OkHttpTransport's default client now uses longer, explicit timeouts (15s connect / 30s read / 15s write) instead of an unconfigured defaultOkHttpClient(), and is shared (one instance, one connection pool) across every call site instead of one fresh client per call site.- Build, unit tests, and lint pass.
- Live confirmation from the human product owner that the "LGD-P9-CLCI-4K" repository's commit
history now loads instead of failing, that
.cpp/.hfiles open correctly, and that the "커밋" label reads correctly — the build has been reinstalled on the device but not yet re-tested against those exact cases in this session.
Required checks
./gradlew compileDebugKotlin compileDebugUnitTestKotlin
./gradlew testDebugUnitTest
./gradlew assembleDebug lintDebug
Evidence
strings_branchdetail.xml/strings_codebrowser.xml:히스토리→커밋.strings_history.xml:history_permission_deniedreworded to say "커밋 내역".SourceContentClassifier.kt:decodeStrictUtf8replaced bydecodeText(tries UTF-8, then EUC-KR, both strict) built on a new shareddecodeStrict(bytes, charset)helper; newEUC_KRcharset constant.SourceContentClassifierTest.kt: two new tests — EUC-KR-encoded Korean text now classifies asTextwith the correctly decoded string; a byte sequence invalid in both UTF-8 and EUC-KR still classifies asUnsupportedEncoding. The two pre-existing "malformed UTF-8" tests were checked (not just left alone) to confirm their specific byte sequences are also invalid under EUC-KR's own lead/trail-byte rules, so they still correctly exercise the "both attempts fail" path rather than accidentally now succeeding via the new fallback.HttpTransport.kt: new privatesharedOkHttpClient(lazy, 15s/30s/15s timeouts), nowOkHttpTransport's defaultclientparameter, replacing the previous bareOkHttpClient()../gradlew compileDebugKotlin compileDebugUnitTestKotlin: BUILD SUCCESSFUL../gradlew testDebugUnitTest: BUILD SUCCESSFUL, all unit tests passing (including the two new ones above)../gradlew assembleDebug lintDebug: BUILD SUCCESSFUL, 0 lint errors.- Installed on the human product owner's device over wireless
adb(adb install -r, streamed install succeeded); not yet re-tested live against the specific repository/files that originally reported these bugs in this session.
Known limitations
- The error message itself still doesn't distinguish "genuinely offline" from "reachable but slow" — a request that's still too slow even at the new 30-second read timeout (or a device that's truly offline) both still render as the same "서버에 연결할 수 없습니다" message. The fix here is to stop the common case of a merely-slow-but-working NAS response from being misclassified, not a full error-taxonomy redesign.
- EUC-KR only, not the larger CP949/MS949 superset — a file using a Hangul syllable or symbol only
representable in CP949's extended completion range (not present in the older EUC-KR/KS X 1001
set) will still classify as
UnsupportedEncoding. This was a deliberate choice given Android's charset-availability guarantees (see Design notes), not an oversight. - No caching or ETag/conditional-request support was added — the connection-pool sharing reduces handshake overhead for repeated calls to the same host, but each call still does a full request/ response round trip; a directory or commit list re-opened a second time still re-fetches from scratch.
- Live confirmation against the exact repository and files that originally reported these bugs is still pending in this session (see Acceptance criteria).
Review findings
None yet — pending Codex/human review.