0baf831b58f8

C1 chrome fix: EntryDetailScreen's onBack is now nullable - null when this entry is the post-unlock landing surface (no previous back-stack entry, detected via navController.previousBackStackEntry), showing a lock-vault icon instead of a misleading back-to-list arrow. Real back arrow still shows correctly when an entry is reached by tapping a vault-list row (a genuine push). Both cases live-verified

time
2026-07-24 06:44 UTC · 3 months ago
machine
koh2
agent
claude-sonnet-5
session
critical-corrections-c1-c7
parent
d1828261164d
3 files app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+9 −1app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+40 −13app/src/main/java/com/peachpasswords/ui/vault/EntryDetailViewModel.kt+10 −0
app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+9 −1
@@ -151,10 +151,18 @@151151             route = PeachNav.EntryDetail.ROUTE,152152             arguments = listOf(navArgument("id") { type = NavType.StringType }),153153         ) { backStack ->154+            // C1: when this entry is the post-unlock landing surface, its155+            // unlock navigation popped Lock off the stack (see the Lock156+            // composable below), so there's no previous entry — it's the157+            // deck's root, not a screen pushed from the vault list, and158+            // shouldn't offer a "back to list" affordance that implies159+            // otherwise. Only entries reached by tapping a vault-list row160+            // (a real push) get a real back target.161+            val isRoot = navController.previousBackStackEntry == null154162             EntryDetailScreen(155163                 id = backStack.arguments?.getString("id").orEmpty(),156164                 onNavigate = nav,157-                onBack = { navController.navigate(PeachNav.VaultList.route) },165+                onBack = if (isRoot) null else ({ navController.navigate(PeachNav.VaultList.route) }),158166             )159167         }160168         composable(

view file

app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+40 −13
@@ -39,6 +39,7 @@3939 import androidx.compose.material.icons.automirrored.rounded.ArrowBack4040 import androidx.compose.material.icons.automirrored.rounded.ArrowForward4141 import androidx.compose.material.icons.outlined.DeleteOutline42+import androidx.compose.material.icons.outlined.Lock4243 import androidx.compose.material3.Icon4344 import androidx.compose.material3.Text4445 import androidx.compose.runtime.Composable@@ -97,7 +98,10 @@9798 fun EntryDetailScreen(9899     id: String,99100     onNavigate: (PeachNav) -> Unit,100-    onBack: () -> Unit,101+    // Null when this entry is the post-unlock landing surface (C1: entry deck102+    // is primary, not a child of the vault list) — there's genuinely nothing103+    // to go "back" to, so the top chrome shows a lock affordance instead.104+    onBack: (() -> Unit)?,101105     vm: EntryDetailViewModel = hiltViewModel(),102106 ) {103107     val entry by vm.entry.collectAsState()@@ -131,8 +135,13 @@131135                     entry = e,132136                     confirmingDelete = confirmingDelete,133137                     onBack = onBack,138+                    onLock = vm::lock,134139                     onDelete = {135-                        if (confirmingDelete) vm.delete(onDone = onBack) else confirmingDelete = true140+                        if (confirmingDelete) {141+                            vm.delete(onDone = onBack ?: { onNavigate(PeachNav.VaultList) })142+                        } else {143+                            confirmingDelete = true144+                        }136145                     },137146                 )138147                 Spacer(Modifier.height(PeachSpacing.lg))@@ -364,7 +373,8 @@364373 private fun EntryDetailTopBar(365374     entry: com.peachpasswords.vault.VaultEntry?,366375     confirmingDelete: Boolean,367-    onBack: () -> Unit,376+    onBack: (() -> Unit)?,377+    onLock: () -> Unit,368378     onDelete: () -> Unit,369379 ) {370380     val domain = remember(entry?.url, entry?.title) { entry?.displayDomain().orEmpty() }@@ -376,16 +386,33 @@376386             else -> entry.title377387         },378388         leading = {379-            Icon(380-                Icons.AutoMirrored.Rounded.ArrowBack,381-                contentDescription = "back",382-                tint = peachColors.T0,383-                modifier = Modifier384-                    .size(40.dp)385-                    .clip(CircleShape)386-                    .clickable(onClick = onBack)387-                    .padding(PeachSpacing.sm),388-            )389+            if (onBack != null) {390+                Icon(391+                    Icons.AutoMirrored.Rounded.ArrowBack,392+                    contentDescription = "back",393+                    tint = peachColors.T0,394+                    modifier = Modifier395+                        .size(40.dp)396+                        .clip(CircleShape)397+                        .clickable(onClick = onBack)398+                        .padding(PeachSpacing.sm),399+                )400+            } else {401+                // This entry is the post-unlock landing surface, not pushed402+                // from the vault list — there's nothing to go "back" to, so403+                // the chrome offers the one action that actually makes sense404+                // here instead (lock vault list's own affordance).405+                Icon(406+                    Icons.Outlined.Lock,407+                    contentDescription = "Lock vault",408+                    tint = peachColors.T0,409+                    modifier = Modifier410+                        .size(40.dp)411+                        .clip(CircleShape)412+                        .clickable(onClick = onLock)413+                        .padding(PeachSpacing.sm),414+                )415+            }389416             if (entry != null && !confirmingDelete) {390417                 FaviconInitials(source = entry.url ?: entry.title, size = 16.dp)391418             }

view file

app/src/main/java/com/peachpasswords/ui/vault/EntryDetailViewModel.kt+10 −0
@@ -12,6 +12,7 @@1212 import com.peachpasswords.vault.LastViewedEntryStore1313 import com.peachpasswords.vault.TOTPGenerator1414 import com.peachpasswords.vault.VaultEntry15+import com.peachpasswords.vault.VaultLockTimer1516 import com.peachpasswords.vault.VaultRepository1617 import dagger.hilt.android.lifecycle.HiltViewModel1718 import dagger.hilt.android.qualifiers.ApplicationContext@@ -36,9 +37,18 @@3637     @ApplicationContext private val appContext: Context,3738     private val lastViewed: LastViewedEntryStore,3839     private val license: LicenseRepository,40+    private val lockTimer: VaultLockTimer,3941     savedStateHandle: SavedStateHandle,4042 ) : ViewModel() {4143     private val id: String = savedStateHandle.get<String>("id").orEmpty()44+45+    /** Lock the vault directly from the entry-deck landing surface (C1: this46+     * screen has no "back" when it's the post-unlock root, so it needs its47+     * own lock affordance rather than relying on a parent screen's). */48+    fun lock() {49+        lockTimer.cancel()50+        vault.lock()51+    }4252 4353     init {4454         // Remember this as the entry to reopen on the next unlock (brief Fix 3).

view file