The READ_CONTACTS permission (used only for recipient autocomplete) is currently requested lazily in the compose screen: ComposeScreen.kt fires a LaunchedEffect(Unit) on screen open (~line 101) that checks the permission and, if not granted, immediately launches the system dialog. Because it is keyed on Unit, it re-fires every time the compose screen opens — on minSdk 29/30 (no auto-permanent-deny), a user who declined without checking "don't ask again" gets re-prompted on every compose.
Proposal
Move the contacts-access request to a dedicated, clearly skippable step in the onboarding flow (alongside the existing battery-optimization step), requested once. Compose then just reads the current permission/autocomplete state and never prompts.
Notes
Degradation is already graceful (this is UX, not a stability fix): ContactsRepository.search() returns empty without permission (wrapped in runCatching), ComposeViewModel.searchContacts() guards on contactsAllowed, and the suggestion list only renders when non-empty.
Contacts is optional — the onboarding step must be skippable and the app fully usable without it.
Companion tickets: the onboarding step should carry a rationale (see the rationale follow-up) and there should be a way to enable it later after declining (see the re-enable follow-up).
Remove the per-compose-open LaunchedEffect trigger as part of this.
Origin: investigation of the contacts-denial path (compose recipient autocomplete).
The `READ_CONTACTS` permission (used only for recipient autocomplete) is currently requested **lazily in the compose screen**: `ComposeScreen.kt` fires a `LaunchedEffect(Unit)` on screen open (~line 101) that checks the permission and, if not granted, immediately launches the system dialog. Because it is keyed on `Unit`, it **re-fires every time the compose screen opens** — on minSdk 29/30 (no auto-permanent-deny), a user who declined without checking "don't ask again" gets re-prompted on every compose.
## Proposal
Move the contacts-access request to a **dedicated, clearly skippable** step in the onboarding flow (alongside the existing battery-optimization step), requested **once**. Compose then just reads the current permission/autocomplete state and never prompts.
## Notes
- Degradation is already graceful (this is UX, not a stability fix): `ContactsRepository.search()` returns empty without permission (wrapped in `runCatching`), `ComposeViewModel.searchContacts()` guards on `contactsAllowed`, and the suggestion list only renders when non-empty.
- Contacts is optional — the onboarding step must be skippable and the app fully usable without it.
- Companion tickets: the onboarding step should carry a rationale (see the rationale follow-up) and there should be a way to enable it later after declining (see the re-enable follow-up).
- Remove the per-compose-open `LaunchedEffect` trigger as part of this.
Origin: investigation of the contacts-denial path (compose recipient autocomplete).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The
READ_CONTACTSpermission (used only for recipient autocomplete) is currently requested lazily in the compose screen:ComposeScreen.ktfires aLaunchedEffect(Unit)on screen open (~line 101) that checks the permission and, if not granted, immediately launches the system dialog. Because it is keyed onUnit, it re-fires every time the compose screen opens — on minSdk 29/30 (no auto-permanent-deny), a user who declined without checking "don't ask again" gets re-prompted on every compose.Proposal
Move the contacts-access request to a dedicated, clearly skippable step in the onboarding flow (alongside the existing battery-optimization step), requested once. Compose then just reads the current permission/autocomplete state and never prompts.
Notes
ContactsRepository.search()returns empty without permission (wrapped inrunCatching),ComposeViewModel.searchContacts()guards oncontactsAllowed, and the suggestion list only renders when non-empty.LaunchedEffecttrigger as part of this.Origin: investigation of the contacts-denial path (compose recipient autocomplete).