- '히스토리' 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>
149 lines
10 KiB
Markdown
149 lines
10 KiB
Markdown
# 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.kt` for the shared,
|
|
longer-timeout `OkHttpClient`.
|
|
- 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, the `feature/history` package) 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`, never `REPLACE`, 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 own
|
|
`Charset` documentation 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 throwing `UnsupportedCharsetException` at 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. `0xFF` as a lead
|
|
byte, valid in neither) still correctly falls through to `UnsupportedEncoding` — 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's `ViewModel.Factory`, plus `AuthSession`)
|
|
constructed its own fresh, default-configured `OkHttpClient()` — 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) through `GiteaRepositoryBrowserApi.authenticated`/`GiteaHistoryApi.get`,
|
|
both of which catch `IOException` broadly (this necessarily includes `SocketTimeoutException`)
|
|
and wrap it as `ApiFailure.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 shared
|
|
`OkHttpClient` (15s connect / 30s read / 15s write, versus the previous unconfigured 10s/10s/10s)
|
|
is now `OkHttpTransport`'s default, replacing the previous `OkHttpClient()` 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
|
|
|
|
- [x] The branch-detail tab and the code-browser's "view this file's history" button both read
|
|
"커밋" instead of "히스토리".
|
|
- [x] `history_permission_denied`'s message no longer contains the word "히스토리".
|
|
- [x] A `.cpp`/`.h` (or any text) file encoded in EUC-KR, not UTF-8, renders as text instead of
|
|
`UnsupportedEncoding`.
|
|
- [x] A byte sequence invalid in both UTF-8 and EUC-KR still correctly classifies as
|
|
`UnsupportedEncoding` — the fallback doesn't swallow genuinely unrecognized encodings.
|
|
- [x] `OkHttpTransport`'s default client now uses longer, explicit timeouts (15s connect / 30s
|
|
read / 15s write) instead of an unconfigured default `OkHttpClient()`, and is shared (one
|
|
instance, one connection pool) across every call site instead of one fresh client per call
|
|
site.
|
|
- [x] 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`/`.h` files 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
|
|
|
|
```text
|
|
./gradlew compileDebugKotlin compileDebugUnitTestKotlin
|
|
./gradlew testDebugUnitTest
|
|
./gradlew assembleDebug lintDebug
|
|
```
|
|
|
|
## Evidence
|
|
|
|
- `strings_branchdetail.xml`/`strings_codebrowser.xml`: `히스토리` → `커밋`.
|
|
- `strings_history.xml`: `history_permission_denied` reworded to say "커밋 내역".
|
|
- `SourceContentClassifier.kt`: `decodeStrictUtf8` replaced by `decodeText` (tries UTF-8, then
|
|
EUC-KR, both strict) built on a new shared `decodeStrict(bytes, charset)` helper; new `EUC_KR`
|
|
charset constant.
|
|
- `SourceContentClassifierTest.kt`: two new tests — EUC-KR-encoded Korean text now classifies as
|
|
`Text` with the correctly decoded string; a byte sequence invalid in both UTF-8 and EUC-KR still
|
|
classifies as `UnsupportedEncoding`. 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 private `sharedOkHttpClient` (lazy, 15s/30s/15s timeouts), now
|
|
`OkHttpTransport`'s default `client` parameter, replacing the previous bare `OkHttpClient()`.
|
|
- `./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.
|