fix: use saveable repository list keys

This commit is contained in:
최민영 2026-08-26 21:33:07 +09:00
parent 5778dc9b2a
commit 5e414a663a
4 changed files with 73 additions and 1 deletions

View File

@ -223,7 +223,7 @@ private fun RepositoryContent(
if (state.mode == RepositoryListMode.ALL && state.organizations.isNotEmpty()) { if (state.mode == RepositoryListMode.ALL && state.organizations.isNotEmpty()) {
item { OrganizationFilterRow(state.organization, state.organizations, onSelectOrganization) } item { OrganizationFilterRow(state.organization, state.organizations, onSelectOrganization) }
} }
items(state.repositories, key = { it.coordinates }) { repository -> items(state.repositories, key = { repositoryLazyListKey(it.coordinates) }) { repository ->
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@ -265,6 +265,9 @@ private fun RepositoryContent(
} }
} }
internal fun repositoryLazyListKey(coordinates: RepositoryCoordinates): String =
"${coordinates.owner}/${coordinates.name}"
@Composable @Composable
private fun OrganizationFilterRow( private fun OrganizationFilterRow(
selected: String?, selected: String?,

View File

@ -0,0 +1,23 @@
package com.ubisam.ubgitea.feature.repositories
import com.ubisam.ubgitea.domain.model.RepositoryCoordinates
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Test
class RepositoryListScreenTest {
@Test
fun `lazy list key is a stable saveable string`() {
val coordinates = RepositoryCoordinates.of("team", "mobile")
assertEquals("team/mobile", repositoryLazyListKey(coordinates))
}
@Test
fun `lazy list keys distinguish the same repository name under different owners`() {
val first = repositoryLazyListKey(RepositoryCoordinates.of("team-a", "mobile"))
val second = repositoryLazyListKey(RepositoryCoordinates.of("team-b", "mobile"))
assertNotEquals(first, second)
}
}

View File

@ -305,3 +305,11 @@ This is the append-only implementation and handoff record shared by Codex and Cl
- Known limitations: Physical-device callback verification is still required. Gitea star/unstar mutation is intentionally unavailable with the restored read-only token. - Known limitations: Physical-device callback verification is still required. Gitea star/unstar mutation is intentionally unavailable with the restored read-only token.
- Next recommended task: Install the rebuilt debug APK, clear UBGitea app data once, complete login approval, and verify repository loading. - Next recommended task: Install the rebuilt debug APK, clear UBGitea app data once, complete login approval, and verify repository loading.
- Next owner: Human product owner for device verification, then Claude for review. - Next owner: Human product owner for device verification, then Claude for review.
## 2026-08-26 — TASK-020 repository-list crash fix
- Agent and role: Codex, incident resolver.
- Root cause: The repository `LazyColumn` used `RepositoryCoordinates` as its item key. Compose passes lazy-item keys to Android saved state, but this domain object is not a `Bundle`-saveable type, causing an uncaught main-thread `IllegalArgumentException` as soon as authenticated rows rendered.
- Implementation: Repository rows now use the stable string key `owner/name`; repository components cannot contain `/`, so the key remains unique across owners and repositories while being Android-saveable.
- Regression coverage: Added tests for deterministic key encoding and for equal repository names under different owners.
- Checks: Focused tests and `assembleDebug testDebugUnitTest lintDebug` passed. The APK installed wirelessly over the authenticated device, `MainActivity` remained resumed, and the cleared crash buffer stayed empty. Final visual inspection is pending because the handset locked.

View File

@ -0,0 +1,38 @@
# TASK-020: Prevent repository-list crash after login
Status: implemented; unlocked-screen confirmation pending
Owner: Codex
Reviewer: Claude
## Outcome
After a successful OAuth login, the repository list renders without terminating the app, and subsequent launches remain usable.
## Risk
Compose lazy-list item keys participate in saved UI state on Android. A key that cannot be stored in a `Bundle` throws on the main thread while the list is composed.
## Writable paths
- `app/src/main/java/com/ubisam/ubgitea/feature/repositories/RepositoryListScreen.kt`
- matching unit tests
- this task file and `docs/development-log.md`
## Acceptance criteria
- [x] Repository rows use a unique, stable, Android-saveable key.
- [x] A regression test covers repositories with the same name under different owners.
- [x] Debug build, unit tests, and Android lint pass.
- [ ] The rebuilt APK launches and renders the authenticated repository list on the physical device.
- [x] Repository secret-pattern scan reports no credential material.
## Incident evidence
Physical-device crash buffer on 2026-08-26 reported `IllegalArgumentException: Type of the key RepositoryCoordinates(...) is not supported` from `SaveableStateHolderImpl.SaveableStateProvider`, originating while the repository `LazyColumn` was composed.
## Verification evidence
- Focused regression tests passed.
- `assembleDebug testDebugUnitTest lintDebug` passed.
- The rebuilt APK installed over the existing authenticated installation successfully; the app process remained alive with `MainActivity` resumed and the post-launch crash buffer empty.
- The handset locked before the rendered repository rows could be inspected, so visual confirmation remains pending.