Skip to content

fix(mobile): recover when the active organization membership is removed - #8103

Merged
iscekic merged 12 commits into
mainfrom
drain/membership-removed-recovery
Oct 22, 2026
Merged

iscekic merged 12 commits into
mainfrom
drain/membership-removed-recovery

Conversation

@iscekic

@iscekic iscekic commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Change

When the server removes the user's membership in the active organization, the app previously stayed on the dead organization: Home showed an access-denied live section with only a back action, and the Profile credits row lost its organization name and balance ("Failed to load balance. Tap to retry.") with a disabled organization row.

Two additions make the app recover:

  1. Provider reconciliation (apps/mobile/src/lib/organization-context.tsx): the provider now compares each settled organizations.list snapshot against the one the current selection was published with. A new snapshot that no longer names the stored organization switches to the first remaining membership — or Personal, writing the explicit-Personal marker — through the existing setOrganizationId path (persistence, activity-token cleanup, glanceable blanking all reused), and announces the new context with a localized toast (organization.boundary.membershipLostNotice): "You no longer have access to this organization. Switched to Org Beta."

  2. List refresh on permission loss (apps/mobile/src/lib/query-client.ts): the cached list can still name the org after the server already dropped the membership, so the provider would have nothing to act on until the next foreground freshness pass. A permission-denied error (FORBIDDEN / procedure-level UNAUTHORIZED) on an org-scoped query now invalidates the shared organizations.list once, so the fresh snapshot reaches the provider immediately. A list still holding the org (role-scoped denial, org intact) changes nothing: the fresh snapshot matches the selection's own, and the existing access-denied surface stays.

New key organization.boundary.membershipLostNotice in en.json, translated into all 86 non-English catalogs (placeholder parity kept).

End-to-end verification (Android emulator, local stack, owned fake account)

Signed in as a fake account member of two seeded organizations (Org Alpha active, Org Beta secondary); credits row showed "Org Alpha" with a loaded balance.

After deleting the member's Org Alpha membership row on the local server and returning to Home:

  • The app switched to Org Beta and showed the localized toast: "You no longer have access to this organization. Switched to Org Beta." Home rendered the live section normally — no access-denied dead end.
  • Profile then showed "Org Beta" as the credits context with a loaded balance, and "View organization / Org Beta" — no generic name, no failed balance, no disabled row.

Checks: pnpm --filter kilo-app typecheck clean; lint 0 errors; check:unused clean; check:plurals clean; vitest suites for organization-context (8, including four new recovery cases), query-client (27, including two new list-refresh cases), and the home mounted suite (57) all pass — 92/92.

Screenshots: baseline "Org Alpha" credits row, the recovery toast on Home, and the recovered "Org Beta" credits row are attached in a follow-up comment.

Sync with main

The branch merged main (through cef32d4d04) so it keeps the newer organization surfaces:

  • Every locale catalog keeps both this change's organization.boundary.membershipLostNotice and main's new organization strings, with sv resolved by taking main's retranslated boundary block and inserting the notice in its order.
  • Main's PR-review context line (added after this branch was cut) is aligned: a stored organization that left the list now shows the recovered membership instead of the unavailable scope, because the provider switches before that line renders. Its suite mocks the toast adapter the provider imports, like the other provider-mounted suites.
  • A restoration test now resolves the memberships list with the stored organization, so it no longer raced the lost-membership switch.

Re-run on the merged tree: apps/mobile vitest 1241 files / 18439 tests pass, pnpm --filter kilo-app typecheck clean, pnpm check:i18n reports 87 catalogs matching en.json, format:check clean.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: When the active organization disappears, which context should the app switch to?
    Answer: First remaining membership (Recommended)
    Confidence: 0.80 · Precedent: Do the least work possible (session 7f3470e0ba6f: "do the least amount of work possible, with no drop in quality"). This reuses the existing default-resolution rule and matches the brief.
  2. Question: How should the app tell the user the context was switched?
    Answer: Toast notice (Recommended)
    Confidence: 0.85 · Precedent: Localize every string in the mobile app (session dcb3f9170164: "the mobile app always must respect i18n"). The brief requires a localized notice.
  3. Question: When the fallback lands on Personal, should it write the explicit-Personal marker?
    Answer: Write the marker (Recommended)
    Confidence: 0.70 · Precedent: Do the least work possible (session 00eb2a1d3e4b: "skip work that doesn't need to be done"). Reuse the existing switch path and do not add a duplicate recovery branch.
  4. Question: Which FORBIDDEN errors should force a refresh of the organizations list (the trigger that catches a server-side membership removal while the cached list still contains the org)?
    Answer: Any org-scoped FORBIDDEN (Recommended)
    Confidence: 0.80 · Precedent: Fix the class of issue on every affected surface (session 75f8833fe8c1: "fix this for all ... on all surfaces"). React just in time, not on the next poll (session 55c9c80332a7: "react just in time, no polling").
  5. Question: When the app restores a stored organization and the FIRST fetched organizations list does not contain it (cold start, read cache expired), should the app auto-switch to the remaining context with the toast, or keep the existing "Organization unavailable" surface with a manual picker?
    Answer: Auto-switch on first list (Recommended)
    Confidence: 0.75 · Precedent: Autonomy: 'Fix the root cause and the class of the issue, not the single symptom' (session 00eb2a1d3e4b, 'fix the class'); Speed: 'Replace old paths instead of keeping them alongside' (session ad6a3f589364, 'alongside? why not instead?')

… disappears

A stored organization selection can outlive the membership itself: the
host removes the member (or the organization is deleted) while the app
still holds the id. Home then stays on the dead organization with an
access-denied live section, and the profile credits row loses its
organization name and balance.

OrganizationProvider now compares each settled organizations.list
snapshot against the one the current selection was published with. A new
snapshot missing the stored selection switches to the first remaining
membership (or Personal, with the explicit marker) through the existing
selection path and announces the new context with a localized toast.

Because the cached list can still name the org after the server already
dropped the membership, a permission denial on an org-scoped query now
also refetches the shared list once, so the provider sees the change
instead of waiting for the next foreground freshness pass.
The permission-loss trigger refetched with a stale filter, which skipped
a list inside its freshness window; invalidateQueries marks the payload
stale and refetches active observers regardless of age. Also hoists the
pure key/error helpers out of the describe block and satisfies the
promise lint rules in the touched tests.
@iscekic iscekic added drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer labels Oct 19, 2026
@iscekic

iscekic commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

bot: End-to-end recovery on the Android emulator (local stack, owned fake account, member of two seeded organizations).

Signed in with the first organization active; the Profile credits row showed that organization's name with a loaded balance.

After the server removed that membership and the app refreshed:

  1. Home switched to the remaining organization and showed the recovery toast — the live section rendered normally, no access-denied dead end.
  2. Profile then showed the remaining organization as the credits context with a loaded balance and a working organization row — no generic name, no failed balance, no disabled row.

Baseline credits card

Recovery notice on the home tab

Recovered credits card

Comment thread apps/mobile/src/lib/query-client.ts Resolved
@kilo-code-bot

kilo-code-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit is a single test-fixture correction that makes the stored-selection test's list include the saved organization, matching the new first-snapshot recovery behavior; the previously reported findings are resolved in current code and no new subscriptions, timers, or listeners are introduced.

Assumptions: [INFERENCE] Vitest suites were not executed in this read-only review; verification is from the current source and the PR's reported checks.

Files Reviewed (4 files)
  • apps/mobile/src/lib/organization-context.mounted.test.tsx - 0 issues (incremental change)
  • apps/mobile/src/lib/organization-context.tsx - 0 issues (targeted re-verification; first-snapshot detection and error-guarded baseline confirmed at current HEAD)
  • apps/mobile/src/lib/query-client.ts - 0 issues (targeted re-verification; cancelRefetch: false confirmed)
  • apps/mobile/src/lib/query-client.test.ts - 0 issues (targeted re-verification; isInvalidated assertion confirmed)
Previous Review Summaries (6 snapshots, latest commit d0a74a3)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit d0a74a3)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit is test-only: it mocks the toast adapter the provider imports and aligns the PR-review context-line case with the provider's lost-membership recovery, and the earlier findings remain fixed in current code; no new subscriptions, listeners, or timers are added, so there is no additional memory-leak surface.

Assumptions: [INFERENCE] The vitest suites were not executed in this read-only review; the PR reports organization-context, query-client, and mounted suites passing on this head.

Files Reviewed (4 files)
  • apps/mobile/src/components/pr-review/pr-review-context-line.mounted.test.tsx - 0 issues (incremental change)
  • apps/mobile/src/lib/organization-context.tsx - 0 issues (targeted re-verification; first-snapshot detection and error-guarded baseline remain fixed)
  • apps/mobile/src/lib/query-client.ts - 0 issues (targeted re-verification; cancelRefetch: false remains present)
  • apps/mobile/src/lib/query-client.test.ts - 0 issues (targeted re-verification; isInvalidated assertion remains present)

Previous review (commit 7443c66)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change only seeds the shared memberships list in the organization credits expiry test so the provider keeps the staged selection, matching the provider's current recovery semantics; the four earlier findings are resolved in current code and no new defect or memory leak was introduced.

Assumptions: none.

Files Reviewed (4 files)
  • apps/mobile/src/components/profile-credits-card.mounted.test.tsx - 0 issues (incremental change)
  • apps/mobile/src/lib/organization-context.tsx - 0 issues (targeted re-verification; first-snapshot detection and pre-guard baseline findings are fixed)
  • apps/mobile/src/lib/query-client.ts - 0 issues (targeted re-verification; cancelRefetch: false is present)
  • apps/mobile/src/lib/query-client.test.ts - 0 issues (targeted re-verification; isInvalidated assertion is present)

No new subscriptions, listeners, or timers are added by the incremental change, so the PR introduces no additional memory-leak surface.

Previous review (commit 00a9f40)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit only clarifies comments in OrganizationProvider and updates four mounted suites to seed the fetched list the provider reconciles against; the earlier findings remain fixed in the current code. No memory leaks were introduced: the recovery path adds no subscriptions or listeners, and the provider's refs are reset on sign-out and unmount.

Files Reviewed (5 files)
  • apps/mobile/src/lib/organization-context.tsx - 0 issues
  • apps/mobile/src/lib/organization-context.mounted.test.tsx - 0 issues
  • apps/mobile/src/lib/deep-link-launch.mounted.test.tsx - 0 issues
  • apps/mobile/src/components/context-control.mounted.test.tsx - 0 issues
  • apps/mobile/src/components/profile-credits-card.mounted.test.tsx - 0 issues

Previous review (commit 5124f72)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The follow-up commit only adds the organization.boundary.membershipLostNotice string to the 86 non-English catalogs, all with the {{target}} placeholder preserved, and the four prior findings are fixed in the current code.

Files Reviewed (87 files)
  • apps/mobile/src/i18n/locales/*.json (86 files) - 0 issues
  • apps/mobile/src/lib/organization-context.tsx - targeted re-check of prior findings, all fixed
  • apps/mobile/src/lib/query-client.ts - prior cancelRefetch: false finding fixed
  • apps/mobile/src/lib/query-client.test.ts - prior assertion finding fixed (isInvalidated now asserted)

Previous review (commit 2f369b3)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The follow-up commits fix all four prior findings: a restored selection is now evaluated against its first settled snapshot, snapshots seen during an error are re-evaluated after it clears, the list invalidation joins an in-flight fetch, and the non-org test now asserts isInvalidated. The new cold-start case is covered by a test.

Files Reviewed (4 files)
  • apps/mobile/src/lib/organization-context.tsx - 0 issues
  • apps/mobile/src/lib/organization-context.test.ts - 0 issues
  • apps/mobile/src/lib/query-client.ts - 0 issues
  • apps/mobile/src/lib/query-client.test.ts - 0 issues

Previous review (commit 710bf49)

Status: 4 Issues Found | Recommendation: Address before merge

Executive Summary

The lost-membership recovery is sound for a running app, but a selection restored after the 24h read-cache expiry becomes its own baseline, so the dead organization is never recovered; three smaller issues cover a snapshot consumed during an error, repeated list-fetch restarts, and a vacuous test assertion.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/lib/organization-context.tsx 247 A restored selection has no prior snapshot, so when the persisted read cache has expired the first settled list (already missing the org) becomes the baseline and structurally-shared refetches never trigger recovery.

SUGGESTION

File Line Issue
apps/mobile/src/lib/organization-context.tsx 255 A snapshot observed while state.error is set is recorded as the baseline before the guard skips detection, losing the recovery permanently.
apps/mobile/src/lib/query-client.ts 244 invalidateQueries defaults to cancelRefetch: true, so concurrent org-scoped denials cancel and restart the shared list fetch repeatedly.
apps/mobile/src/lib/query-client.test.ts 286 The non-org FORBIDDEN test asserts dataUpdatedAt (unchanged by invalidation) and cannot detect a wrongly scheduled refresh.
Files Reviewed (5 files)
  • apps/mobile/src/i18n/locales/en.json - 0 issues
  • apps/mobile/src/lib/organization-context.tsx - 2 issues
  • apps/mobile/src/lib/organization-context.test.ts - 0 issues
  • apps/mobile/src/lib/query-client.ts - 1 issue
  • apps/mobile/src/lib/query-client.test.ts - 1 issue

No memory leaks found: the provider's new refs hold the shared query array for the provider lifetime only, are reset on sign-out and restore, and the invalidation adds no subscription or listener.

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 41.6K · Output: 3.3K · Cached: 301.7K

Review guidance: REVIEW.md from base branch main

A selection restored from storage had no list baseline, so on a cold
start without a persisted read cache the first live snapshot already
missing the organization became the baseline and recovery never armed.
A restored selection now evaluates its very first snapshot; a manual
pick records the snapshot it was made against as its baseline.

The detection effect records the snapshot only after its guards pass, so
a snapshot observed during a save or restore error is re-evaluated once
the error clears. The list invalidation now passes cancelRefetch false
so concurrent denials cannot cancel each other's shared fetch, and the
non-org test asserts the invalidation flag instead of the fetch
timestamp.

@iscekic iscekic left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bot: Addressed all four threads in 2f369b3:

  1. Cold-start recovery (organization-context.tsx): a selection restored from storage now has no list baseline, so its very first settled snapshot is evaluated — a snapshot already missing the stored organization arms the recovery instead of becoming the baseline. A manual pick records the snapshot it was made against as its baseline, so picker-offered orgs stay trusted. Regression test added ("recovers a stored selection whose very first snapshot already misses it").
  2. Error-state baseline (organization-context.tsx): the snapshot is now recorded only after every guard passes, so a snapshot observed during a save/restore error is re-evaluated once the error clears.
  3. Concurrent denials (query-client.ts): the invalidation passes cancelRefetch: false, so several org-scoped denials around the same time cannot cancel each other's shared list fetch.
  4. Test assertion (query-client.test.ts): the non-org case now asserts isInvalidated is false instead of dataUpdatedAt.

Adds organization.boundary.membershipLostNotice to all 86 non-English
catalogs with the {{target}} placeholder kept exactly once per locale,
matching the sibling boundary copy's tone and formality.
@iscekic iscekic added merge:human Substantive functional risk requires human review. and removed merge:human Substantive functional risk requires human review. labels Oct 19, 2026
A stored organization selection missing from the first fetched list now
switches to the first remaining membership (or Personal) instead of
rendering the dead selection with a generic credits name and a failed
balance; the picker and the boundary surfaces then show the recovered
context. A manual selection records the snapshot it was made against as
its baseline, and a snapshot observed during a save or restore error is
re-evaluated once the error clears.

The mounted suites that pinned the old stored-missing contract update to
the new one: the provider recovers before the sheet or card renders, so
the staged selections now seed the fetched list they are asserted
against. The four suites that reach react-native's Flow source through
the toast adapter mock the adapter at that edge.
@iscekic

iscekic commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Cold-start recovery on the Android emulator (local stack, owned fake account, member of two seeded organizations).

Signed in with the first organization active; the Profile credits row showed that organization's name with a loaded balance.

Then the app was closed, the server removed that membership, and the app was relaunched:

  1. Home switched to the remaining organization and showed the recovery notice; the live section rendered normally, with no access-denied dead end.
  2. Profile showed the remaining organization as the credits context, with a loaded balance and a working organization row.

The earlier comment covers the live removal while the app is open; this run covers the restored-selection case on the current head.

Profile credits row with the active organization and a loaded balance

Home after relaunch with the recovery notice naming the remaining organization

Profile credits row showing the remaining organization with a loaded balance

@iscekic iscekic added the merge:human Substantive functional risk requires human review. label Oct 20, 2026
@iscekic iscekic added merge:blocked merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:human Substantive functional risk requires human review. and removed merge:human Substantive functional risk requires human review. merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked labels Oct 20, 2026
@iscekic

iscekic commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Follow-up evidence on the account selector after a membership disappears (Android emulator, local stack, owned fake account, two seeded organizations).

The selector's rows come from the shared membership list, so this run exercised that surface directly.

  1. Baseline: membership in exactly one organization, first remaining organization, personal account for a second capture.
  2. Before removal: the selector listed Personal, Drain Alpha (checked) and Drain Beta.
  3. Server-side removal of the active membership, then back to Home: the live section rendered normally, and Profile's credits row read "Drain Beta" with a loaded balance — no generic account name, no failed balance.
  4. Selector right after that recovery: Personal and Drain Beta, with Drain Beta checked.
  5. Server-side removal of the last membership: Profile's credits row read "Personal" with the personal balance loaded.
  6. Selector with no memberships left: Personal only.

Commands and observation source: adb exec-out uiautomator dump on each screen, cross-checked with the captures below; membership rows deleted directly in the local development database.

Attachments show only the fake seeded organizations and fake balances on http://localhost:3000 over adb reverse.

Profile credits row naming the active organization before removal

Account selector listing Personal and both organizations

Home live section rendering normally after the membership removal

Profile credits row on the remaining organization with a loaded balance

Account selector listing Personal and the remaining organization after recovery

Profile credits row on Personal with the personal balance loaded

Account selector listing Personal when no organization membership remains

…ved-recovery

# Conflicts:
#	apps/mobile/src/i18n/locales/sv.json
…y test

The context provider recovers a stored selection that the first fetched
list omits, so a test that stages an organization selection must publish
that organization in the shared list; otherwise the card renders the
personal credit blocks.
@iscekic iscekic added merge:human Substantive functional risk requires human review. and removed merge:human Substantive functional risk requires human review. labels Oct 21, 2026
…ved-recovery

# Conflicts:
#	apps/mobile/src/components/context-control.mounted.test.tsx
#	apps/mobile/src/i18n/locales/af.json
#	apps/mobile/src/i18n/locales/am.json
#	apps/mobile/src/i18n/locales/ar.json
#	apps/mobile/src/i18n/locales/az.json
#	apps/mobile/src/i18n/locales/be.json
#	apps/mobile/src/i18n/locales/bg.json
#	apps/mobile/src/i18n/locales/bn.json
#	apps/mobile/src/i18n/locales/bs.json
#	apps/mobile/src/i18n/locales/ca.json
#	apps/mobile/src/i18n/locales/ckb.json
#	apps/mobile/src/i18n/locales/cs.json
#	apps/mobile/src/i18n/locales/cy.json
#	apps/mobile/src/i18n/locales/da.json
#	apps/mobile/src/i18n/locales/de.json
#	apps/mobile/src/i18n/locales/el.json
#	apps/mobile/src/i18n/locales/en.json
#	apps/mobile/src/i18n/locales/es.json
#	apps/mobile/src/i18n/locales/et.json
#	apps/mobile/src/i18n/locales/eu.json
#	apps/mobile/src/i18n/locales/fa.json
#	apps/mobile/src/i18n/locales/fi.json
#	apps/mobile/src/i18n/locales/fil.json
#	apps/mobile/src/i18n/locales/fr.json
#	apps/mobile/src/i18n/locales/ga.json
#	apps/mobile/src/i18n/locales/gl.json
#	apps/mobile/src/i18n/locales/gu.json
#	apps/mobile/src/i18n/locales/ha.json
#	apps/mobile/src/i18n/locales/he.json
#	apps/mobile/src/i18n/locales/hi.json
#	apps/mobile/src/i18n/locales/hr.json
#	apps/mobile/src/i18n/locales/ht.json
#	apps/mobile/src/i18n/locales/hu.json
#	apps/mobile/src/i18n/locales/hy.json
#	apps/mobile/src/i18n/locales/id.json
#	apps/mobile/src/i18n/locales/ig.json
#	apps/mobile/src/i18n/locales/is.json
#	apps/mobile/src/i18n/locales/it.json
#	apps/mobile/src/i18n/locales/ja.json
#	apps/mobile/src/i18n/locales/ka.json
#	apps/mobile/src/i18n/locales/kk.json
#	apps/mobile/src/i18n/locales/km.json
#	apps/mobile/src/i18n/locales/kn.json
#	apps/mobile/src/i18n/locales/ko.json
#	apps/mobile/src/i18n/locales/lo.json
#	apps/mobile/src/i18n/locales/lt.json
#	apps/mobile/src/i18n/locales/lv.json
#	apps/mobile/src/i18n/locales/mg.json
#	apps/mobile/src/i18n/locales/mi.json
#	apps/mobile/src/i18n/locales/mk.json
#	apps/mobile/src/i18n/locales/ml.json
#	apps/mobile/src/i18n/locales/mn.json
#	apps/mobile/src/i18n/locales/mr.json
#	apps/mobile/src/i18n/locales/ms.json
#	apps/mobile/src/i18n/locales/mt.json
#	apps/mobile/src/i18n/locales/my.json
#	apps/mobile/src/i18n/locales/nb.json
#	apps/mobile/src/i18n/locales/ne.json
#	apps/mobile/src/i18n/locales/nl.json
#	apps/mobile/src/i18n/locales/om.json
#	apps/mobile/src/i18n/locales/or.json
#	apps/mobile/src/i18n/locales/pa.json
#	apps/mobile/src/i18n/locales/pl.json
#	apps/mobile/src/i18n/locales/ps.json
#	apps/mobile/src/i18n/locales/pt-BR.json
#	apps/mobile/src/i18n/locales/pt.json
#	apps/mobile/src/i18n/locales/ro.json
#	apps/mobile/src/i18n/locales/ru.json
#	apps/mobile/src/i18n/locales/si.json
#	apps/mobile/src/i18n/locales/sk.json
#	apps/mobile/src/i18n/locales/sl.json
#	apps/mobile/src/i18n/locales/so.json
#	apps/mobile/src/i18n/locales/sq.json
#	apps/mobile/src/i18n/locales/sr.json
#	apps/mobile/src/i18n/locales/sv.json
#	apps/mobile/src/i18n/locales/sw.json
#	apps/mobile/src/i18n/locales/ta.json
#	apps/mobile/src/i18n/locales/te.json
#	apps/mobile/src/i18n/locales/th.json
#	apps/mobile/src/i18n/locales/tr.json
#	apps/mobile/src/i18n/locales/uk.json
#	apps/mobile/src/i18n/locales/ur.json
#	apps/mobile/src/i18n/locales/uz.json
#	apps/mobile/src/i18n/locales/vi.json
#	apps/mobile/src/i18n/locales/yo.json
#	apps/mobile/src/i18n/locales/zh-Hans.json
#	apps/mobile/src/i18n/locales/zh-Hant.json
#	apps/mobile/src/i18n/locales/zu.json
@iscekic iscekic removed the merge:human Substantive functional risk requires human review. label Oct 21, 2026
…ecovery

The provider now switches a stored selection that the settled memberships
list no longer names, so the line shows the recovered account instead of
the unavailable scope. Mock the toast adapter the provider imports, as the
other provider-mounted suites do.
The restoration test asserted the stored selection survives a list that
does not name it, which races the provider's lost-membership switch. Give
the resolved list the stored organization so the assertion is stable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer merge:human Substantive functional risk requires human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants