fix(mobile): surface ambiguous deletion transport - #498
Conversation
Greptile SummaryThis PR distinguishes an ambiguous account-deletion transport failure from a confirmed rejection and presents neutral localized messaging without performing unconfirmed teardown.
Confidence Score: 5/5The PR appears safe to merge, with no actionable changed-code defects identified. The new outcome accurately avoids claiming either rejection or acceptance after the DELETE transport rejects, and the UI continues to reserve destructive local teardown for confirmed accepted responses.
|
| Filename | Overview |
|---|---|
| apps/mobile/src/runtime/cloud/deletion.ts | Adds an explicit unknown transport outcome while preserving teardown only for confirmed accepted responses. |
| apps/mobile/src/components/account/delete-account-section.tsx | Presents neutral messaging for ambiguous deletion outcomes and returns before local teardown. |
| apps/mobile/src/runtime/cloud/tests/deletion.test.ts | Updates the network-rejection test to assert the unknown outcome and transport-stage reporting. |
| packages/presentation/i18n/src/locales/en.ts | Adds neutral English copy for an unconfirmed deletion request. |
| packages/presentation/i18n/src/locales/zh-cn.ts | Adds corresponding Simplified Chinese copy for an unconfirmed deletion request. |
Reviews (1): Last reviewed commit: "fix(mobile): surface ambiguous deletion ..." | Re-trigger Greptile
There was a problem hiding this comment.
ℹ️ The change itself is right. Two rough edges on the comment and the copy, plus one question about what happens after the alert.
Reviewed changes — full initial review of the single commit 0fe36729 (5 files) at 0fe3672, the CODE-292 D-38 follow-up to #481.
- New
unknownoutcome —deletion.ts:27-28adds a seventh variant toAccountDeletionOutcome, and thecatcharoundDELETE /account(:123-126) now returns it instead of{ kind: 'failed' }, so a lost response is no longer reported as a rejected request. - Neutral copy, no teardown —
delete-account-section.tsx:39-42alertsdeleteUnknownand returns beforerunAccountDeletionTeardown(), keeping local teardown limited tocompleted/pending. - Rewritten
faileddoc comment — the same hunk narrowsfailedto "the server rejected the request". - Locale strings —
deleteUnknownadded toen.tsandzh-cn.ts, positioned to mirror the union order.
I verified the premise the whole PR rests on, since it is the one thing that would make unknown dead code: better-auth's client builds its $fetch through @better-fetch/fetch's createFetch with no catchAllError (node_modules/better-auth/dist/client/config.mjs:37), and betterFetch's await fetch(context.url, context) is not wrapped in a try/catch (node_modules/@better-fetch/fetch/dist/index.js:600), so a rejected RN fetch really does propagate out of $fetch. The catch is live and the new branch is reachable. I also confirmed the new test is not theatre: reverting :125 back to { kind: 'failed' } fails deletion.test.ts:275 with expected { kind: 'failed' } to deeply equal { kind: 'unknown' }, and 20/20 pass at head (npx vitest run --project mobile apps/mobile/src/runtime/cloud/__tests__/deletion.test.ts). delete-account-section.tsx is the only consumer of the union anywhere in apps/mobile, and the busy flag still resets through the finally on this path, so the new variant leaves no other call site unhandled. This also cleanly resolves the contradiction a prior review flagged on #481, where commit 73431547 had made this branch failed while the neighbouring comment called it ambiguous.
ℹ️ Nothing reconciles the unknown state, and the next tap re-asserts the claim this PR removes
The copy asks the user to check their account status, but if the server did complete the deletion the client stays fully authenticated with device enrollment and tunnel host profiles intact, and nothing re-checks on next launch or foreground. Worse, tapping Delete again re-enters deleteAccount(), where the now-invalid session makes the requirements read fail and returns { kind: 'failed' } — "Could not delete your account. Please try again." That is the exact wrong-confidence message this PR set out to remove, one tap later. The gap predates this PR, so it is a scope question rather than a defect in the diff.
Technical details
# The `unknown` outcome has no recovery path
## Affected sites
- `apps/mobile/src/runtime/cloud/deletion.ts:123-126` — returns `unknown` and stops; nothing
records that a delete may be in flight.
- `apps/mobile/src/components/account/delete-account-section.tsx:39-42` — terminal alert, then
`finally { setBusy(false) }` re-enables the button with no changed state behind it.
- `apps/mobile/src/runtime/cloud/deletion.ts:75-82` — the retry path. A session invalidated by a
server-side deletion makes `requirements.error` truthy, so the second attempt lands on
`{ kind: 'failed' }` and the generic `deleteFailed` copy.
## Required outcome
- A decision, recorded somewhere durable, on what the client owes the user after an `unknown`.
Either the ambiguity is genuinely terminal and the copy is the whole answer, or the client
needs to distinguish "the account is gone" from "the delete failed" on the next attempt.
## Open questions for the human
- Is D-38 scoped to *surfacing* the ambiguity only, with reconciliation tracked separately? If so
it is worth saying so in the PR body, because the second-tap message is the one a user will
actually act on.
- Would a 401/404 on the requirements read after a prior `unknown` be enough to distinguish the
two cases, or does that need a server-side account-status endpoint?ℹ️ Nitpicks
runAccountDeletionTeardown's docstring (deletion.ts:158-159) still enumerates onlyreauthentication-failedandfailedas the kinds it must never be called for.unknownis now the one kind where "the account is still active" is not guaranteed, and a reader taking that list as exhaustive would draw exactly the opposite conclusion from the one this PR encodes. Outside the diff hunk, so noting it here rather than inline.
Claude Opus | 𝕏

Summary
Follow-up to #481 for CODE-292 D-38.
Verification