fix: use saveable repository list keys
This commit is contained in:
parent
5778dc9b2a
commit
5e414a663a
@ -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?,
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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.
|
||||||
|
|||||||
38
tasks/020-repository-list-saveable-key.md
Normal file
38
tasks/020-repository-list-saveable-key.md
Normal 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.
|
||||||
Loading…
x
Reference in New Issue
Block a user