Triage
Confirmed on current main (935c55b37). This is a real iOS mobile bug, not a wash of #9761. A direct saved environment can be restored from Keychain after delete+reinstall, and the Environments trash control never presents its confirm, so the row cannot be forgotten.
What the code does
Settings → Environments lists catalog presentations, including offline/disabled ones. A direct (non-relay) row is ConnectionEnvironmentRow: expand, then the red trash. That is the control in this report, not the T3 Connect long-press path.
Trash calls onRemoveEnvironmentPress in apps/mobile/src/state/use-remote-environment-registry.ts. The only skip before Alert.alert("Remove from this device?", …) is a lookup in connectedEnvironments. The list and the handler share that array, so a visible row should match — but the silent return is still a landmine (any identity/stale-closure miss eats the tap with no feedback).
If the alert did show and the user confirmed, remove is real: environmentCatalog.remove → EnvironmentRegistry.remove → removeConnectionFromCatalog → SecureStore t3code.connection-catalog.v1.
That document is why reinstall does not help. Mobile persists the catalog through Expo SecureStore / iOS Keychain (catalog-store.ts, mobile-secure-storage.ts). app.config.ts also sets keychain-access-groups (#3665). iOS keeps those items across delete+reinstall. Settings → Client Storage only clears thread/cache data; it does not drop the catalog.
Why the confirm never appears
Two stacked presentation problems, both still on main:
- Settings → Environments is a nested formSheet.
SettingsSheetuses form-sheet presentation, then a headerless stack, then Environments. RNAlert.alertpresents aUIAlertControllerfrom the key window — a known miss insidereact-native-screensform sheets / nested nav. The confirm is invoked and never visible, which matches “tap trash, nothing happens.” Other Settings alerts sit on the sheet root; this is the only confirm on the pushed Environments screen. - The trash lives in a Reanimated
FadeInblock that mounts only after expand (ConnectionEnvironmentRow). First taps during layout/FadeIncan be dropped.
Either one is enough for a dead trash on a stale row. The Keychain restore just makes it permanent: the endpoint is offline, Off still leaves the row, reinstall brings it back, and the only forget control is the one that does not present.
First engineering step: reproduce on device/simulator — Settings formSheet → Environments → expand an offline direct row → trash. Confirm whether Alert.alert is called and whether a UIAlertController is sitting behind the sheet.
Related, not duplicates
| Issue / PR | Why it does not close this |
|---|---|
| Open #9761 | T3 Connect unconnected discovery rows have no trash/deregister. This report is a direct saved row that already has a trash button. Do not attribute Closes #12012. |
| Merged #11478 | Switch = disable, not remove. Updated the confirm copy. Trash is still Alert.alert + catalog remove. |
| Merged #11990 | Marks incompatible servers unsupported and disables the switch. Not in the 2026-09-15 App Store binary. Trash stays enabled. |
| Open #8923 | Catalog backup so a decode failure does not wipe environments. Opposite problem. |
| Merged #3665 | Keychain access group. Explains why reinstall restores the row. Not the dead confirm. |
No open PR claims the direct-row confirm / persist path.
Suggested fix
Keep this as a bugfix on apps/mobile (direct Environments row only):
- Always present confirm. Do not
returnwhen the list lookup misses; fall back to the environment id for the label. - Present the confirm in-tree (
showConfirmDialog/ a sheet-local modal), notAlert.alert.ConfirmDialogHostalready exists because native dialogs are unreliable on one platform; the Settings formSheet needs the same treatment on iOS. - Surface a persist failure. If
SecureStore.setItem/removefails, show an error and keep the row. A successful in-memory remove plus a failed Keychain write is how a “removed” environment comes back on the next launch. - Tests: offline direct environment → trash presents confirm; confirm → catalog no longer contains that target; failed SecureStore write → error, row remains.
- Optional escape hatch in Client Storage / Diagnostics: “Forget saved environments on this device,” because Keychain survives reinstall and there is no wipe today.
Do not fold this into #9761 (relay deregister) or #8923 (catalog backup).
Workaround
None in-app. Off only silences the row. Client Storage does not drop credentials. Delete+reinstall restores the same catalog. Until a build with the fix ships, the stale direct row can only be ignored.
Classification: bug · accepted · minor–medium (iOS; cannot forget a Keychain-restored direct environment)
Labels: add accepted, via-triage; keep bug; remove needs-triage
Discord tags: mobile-ios, persistence, ui