Skip to content

[Bug]: iOS trash button does not remove a persisted environment #12012

Description

@joeviezner

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/mobile

Steps to reproduce

  1. Install the current T3 Code iOS app from the App Store.
  2. Open Environments; a direct environment saved by an older installation is restored even after reinstalling the app.
  3. Expand the stale/offline environment.
  4. Tap the red trash button.
  5. Force-quit/relaunch the app and restart the iPhone, then repeat.

The stale row in this reproduction is a direct saved environment, not a T3 Connect relay environment.

Expected behavior

Tapping the trash button should present the “Remove from this device?” confirmation and, after confirmation, remove the saved environment and its local credentials/catalog data.

Actual behavior

Tapping the trash button has no visible effect: no confirmation alert appears and the environment is not removed. The behavior persists after force-quitting the app and restarting the iPhone.

The environment also survives deleting and reinstalling the app. This is consistent with the catalog being stored through Expo SecureStore/iOS Keychain (t3code.connection-catalog.v1), whose data can survive reinstall. The current mobile handler appears intended to call Alert.alert("Remove from this device?", ...), but that alert is never presented in this state.

Relevant source paths:

  • apps/mobile/src/state/use-remote-environment-registry.ts (onRemoveEnvironmentPress)
  • apps/mobile/src/connection/catalog-store.ts
  • apps/mobile/src/persistence/mobile-secure-storage.ts

Impact

Minor bug or occasional failure

Version or commit

Current iOS App Store release installed 2026-09-15 (exact build not shown)

Environment

T3 Code iOS app on iPhone; direct saved environment; stale endpoint offline

Logs or stack traces

Screenshots, recordings, or supporting files

No response

Workaround

None. Force-quit, restart, and reinstall do not clear the entry because the connection catalog persists in iOS Keychain. The stale row can only be ignored.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Sep 16, 2026
  2. juliusmarminge commented on Sep 16, 2026

    @juliusmarminge
    Member

    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:

    1. Settings → Environments is a nested formSheet. SettingsSheet uses form-sheet presentation, then a headerless stack, then Environments. RN Alert.alert presents a UIAlertController from the key window — a known miss inside react-native-screens form 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.
    2. The trash lives in a Reanimated FadeIn block that mounts only after expand (ConnectionEnvironmentRow). First taps during layout/FadeIn can 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):

    1. Always present confirm. Do not return when the list lookup misses; fall back to the environment id for the label.
    2. Present the confirm in-tree (showConfirmDialog / a sheet-local modal), not Alert.alert. ConfirmDialogHost already exists because native dialogs are unreliable on one platform; the Settings formSheet needs the same treatment on iOS.
    3. Surface a persist failure. If SecureStore.setItem / remove fails, 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.
    4. Tests: offline direct environment → trash presents confirm; confirm → catalog no longer contains that target; failed SecureStore write → error, row remains.
    5. 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

  3. added
    acceptedfeature request accepted
    via-triageFiled through npx t3 triage
    and removed
    needs-triageIssue needs maintainer review and initial categorization.
    on Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions