Skip to content

feat(ui): host-owned back stack embedding for the user profile - #836

Closed
mikepitre wants to merge 4 commits into
mike/hosted-navigationfrom
mike/userprofile-host-entries
Closed

feat(ui): host-owned back stack embedding for the user profile#836
mikepitre wants to merge 4 commits into
mike/hosted-navigationfrom
mike/userprofile-host-entries

Conversation

@mikepitre

@mikepitre mikepitre commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Problem

The embedded-navigation handle (#798) lets a host hide Clerk's top bars and drive the profile's internal stack, but the profile still renders its own navigation container. Native Compose apps should be able to put Clerk's screens inside their own NavDisplay — the Android counterpart of the iOS SDK's public UserProfileView(navigationPath:) embedding — so the host owns transitions, predictive back, and the stack itself.

What this adds

A public ClerkUserProfileRoute key and EntryProviderScope.clerkUserProfileEntries(backStack):

val backStack = rememberNavBackStack(Home)

NavDisplay(
  backStack = backStack,
  entryProvider =
    entryProvider {
      entry<Home> { HomeScreen(onOpenProfile = { backStack.add(ClerkUserProfileRoute) }) }
      clerkUserProfileEntries(backStack)
    },
)
  • Clerk's internal destination keys stay internal; hosts interact only with the root key. Internal keys restore in the host's rememberNavBackStack via nav3's reflection-based NavKey serialization.
  • The profile removes only its own segment of the stack on exit: root back navigation, sign-out, and account deletion pop through ClerkUserProfileRoute and never touch host entries (UserProfileState.clearBackStack gains a scoped strategy for this).
  • The profile root shows a back arrow instead of the dismiss X in this mode.
  • navigation3-runtime moves from implementation to api since the new surface exposes its types.
  • The existing self-contained UserProfileView shares the same entry registrations (extracted userProfileChildEntries) and is unchanged in behavior.

Verified end to end on an emulator via the new demo in the prebuilt-ui sample: host home → profile root → Security inside one host-owned NavDisplay, with hardware back unwinding one screen at a time and landing on the intact host screen.

Follow-up (intentionally not in this PR): the same treatment for AuthView. Its AuthState carries cross-screen form state, so per-entry providers would break it — the shared-scope design for that is coming separately.

Modules

source/ui and the prebuilt-ui sample.

🤖 Generated with Claude Code

Note

Add host-owned back stack embedding for the Clerk user profile in NavDisplay

  • Adds clerkUserProfileEntries extension on EntryProviderScope so apps can register Clerk's user profile destinations inside their own NavDisplay and back stack, with Clerk only removing its own keys on exit.
  • Introduces ClerkUserProfileRoute as a serializable NavKey that hosts push onto their NavBackStack to open the profile.
  • Adds exitClerkUserProfile and popToClerkUserProfileRoot back stack helpers and a HostEntryChrome wrapper that applies Clerk's theme and providers to hosted entries.
  • Refactors userProfileChildEntries out of userProfileEntries so child destinations are shared between self-contained and host-owned setups without changing existing behavior.
  • Adds a sample HostStackProfileSample composable in the prebuilt-ui sample app demonstrating the host-owned flow via a full-screen dialog.
  • Behavioral Change: libs.androidx.navigation3.runtime is now exposed as api from the source/ui module, making it transitive for library consumers.

Macroscope summarized 5a7f419.

Hosts that embed UserProfileView or AuthView inside their own navigation
chrome (e.g. the Expo SDK, or apps with their own top app bar) can now pass
the new optional embeddedNavigation parameter. When provided, Clerk's top app
bars are hidden and the host observes stack depth and drives pop()/popToRoot()
through the ClerkEmbeddedNavigation handle. Behavior is unchanged when the
parameter is omitted.

Named embedded rather than hosted because hosted already refers to Clerk's
hosted web pages in the mobile SDKs.

Affects the ui module only.

Obtaining a ClerkEmbeddedNavigation requires opting in to the new
FrameworkIntegrationApi marker (com.clerk.api), the Android counterpart of
the iOS SDK's @_spi(FrameworkIntegration) surface. Components accept the
parameter without opt-in; only handle creation is gated.
EmbeddedNavigationEffects now clears the pop handler only when it still owns
it, so a component leaving composition after a successor registered cannot
tear the successor down, and resets depth to 0 on teardown so the handle no
longer reports canGoBack while pop() would be a no-op.

Affects the ui module only.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3cd9d365-30f3-4909-9600-89b6db2ebfa3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Embedded pop commands mutated the back stack directly, bypassing
AuthState.navigateBack's suppression of the in-progress attempt resume, so
popping a factor screen bounced straight back to it. EmbeddedNavigationEffects
now accepts pop overrides and AuthView routes commands through AuthState.

Affects the ui module only.
Adds ClerkUserProfileRoute and EntryProviderScope.clerkUserProfileEntries so
hosts can render Clerk's profile screens inside their own NavDisplay: the host
owns the back stack, transitions, and predictive back, and Clerk pushes and
removes only its own keys. This is the Android counterpart of the iOS SDK's
public UserProfileView(navigationPath:) embedding.

The profile root shows a back arrow instead of a dismiss affordance in this
mode, account deletion exits only the profile's segment of the stack, and
navigation3-runtime becomes an api dependency since the new surface exposes
its types. Includes a runnable demo in the prebuilt-ui sample.

Affects the ui module and the prebuilt-ui sample.
@mikepitre
mikepitre force-pushed the mike/userprofile-host-entries branch from 9d85a6b to 5a7f419 Compare July 30, 2026 12:58
@mikepitre

Copy link
Copy Markdown
Collaborator Author

Parking this as a draft deliberately: the Expo integration doesn't use host-stack entries (it drives the profile through the FrameworkIntegrationApi-gated handle), so this public API — and the AuthView counterpart it still needs — can wait for a proper design review while the Expo-critical PRs ship. Written by Claude Code

@mikepitre
mikepitre force-pushed the mike/hosted-navigation branch from bf9db79 to 8766d5f Compare July 30, 2026 13:18
@mikepitre mikepitre closed this Jul 30, 2026
@swolfand
swolfand deleted the mike/userprofile-host-entries branch August 20, 2026 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant