From 0c4b720099d5cabe206c59e4aa564f2fc7058e83 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Thu, 18 Jun 2026 11:31:33 +0200 Subject: [PATCH 1/3] docs(nav): spec for expanding nav chrome to more non-flow screens --- .../2026-06-18-expand-nav-chrome-design.md | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-18-expand-nav-chrome-design.md diff --git a/docs/superpowers/specs/2026-06-18-expand-nav-chrome-design.md b/docs/superpowers/specs/2026-06-18-expand-nav-chrome-design.md new file mode 100644 index 0000000000..f6185d5e91 --- /dev/null +++ b/docs/superpowers/specs/2026-06-18-expand-nav-chrome-design.md @@ -0,0 +1,112 @@ +# Expand navigation chrome to more non-flow destinations + +## Goal + +Experiment with showing the global navigation chrome (bottom bar / rail) on more +destinations than the current 9. The chrome should appear on screens that are *not* +part of a self-contained flow (claim flow, termination flow, document-generation flow), +starting with read-only info screens under the Payments and Profile tabs. + +This is a code-level experiment: no runtime feature flag, no A/B toggle. We expand the +opt-in set, build, and evaluate in the running app. + +## Background: how chrome visibility works today + +- The chrome is rendered by `NavSuiteSceneDecoratorStrategy` + (`app/navigation/navigation-compose/src/androidMain/kotlin/com/hedvig/android/navigation/compose/NavSuiteSceneDecorator.kt`). + It is a Navigation 3 `SceneDecoratorStrategy` that wraps a scene with the bar/rail + *only if* the scene's entry metadata carries the `ShowNavBarKey` marker. +- A destination opts in per entry: + `entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { … }`. +- Today the opted-in set is: the 5 tab roots (`HomeKey`, `InsurancesKey`, `ForeverKey`, + `PaymentsKey`, `ProfileKey`) plus 4 deeper screens (`InsuranceContractDetailKey`, + `TerminatedInsurancesKey`, `EurobonusKey`, `ClaimHistoryKey`). +- The highlighted tab is resolved **positionally** by the back stack, not from the key in + isolation: `BackstackController.currentTopLevel` / `owningTopLevelTabForContentKey` + (`app/app/src/main/kotlin/com/hedvig/android/app/navigation/BackstackController.kt`). + This already works for any screen inside the runs model, regardless of whether it shows + the bar. So showing chrome deeper is purely a policy change — no infrastructure change + is required. +- The lone-deep-link gate (`loneDeepLinkChrome`) is unaffected: a screen opened alone as a + deep link still resolves chrome the same way it does today. + +## Decisions + +- **Keep the explicit opt-in model.** The bar stays off by default; we deliberately add + eligibility to chosen screens. No flow can accidentally gain a bar. +- **Mechanism: inline metadata (Option A).** Add + `metadata = NavSuiteSceneDecoratorStrategy.showNavBar()` to each chosen `entry<…>`, + identical to how contract-detail / eurobonus / claim-history already opt in. No new + abstraction (a key marker interface was considered and rejected as premature — the + decorator gate reads scene metadata, not the key). + +## Scope: screens to opt in (first batch) + +In `app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt`: +- `PaymentDetailsKey` +- `PaymentHistoryKey` +- `DiscountsKey` +- `MemberPaymentDetailsKey` + +In `app/feature/feature-profile/src/main/kotlin/com/hedvig/android/feature/profile/tab/ProfileEntries.kt`: +- `ContactInfoKey` +- `InformationKey` +- `LicensesKey` +- `SettingsKey` +- `CertificatesKey` + +**Explicitly excluded:** `ManualChargeKey` / `ManualChargeSuccessKey` (a payment action +with a success step — a mini-flow). + +## The change + +For each of the 9 entries above, add the metadata argument: + +```kotlin +entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { key -> + … +} +``` + +Both files already import `NavSuiteSceneDecoratorStrategy` and already use this pattern on +the tab root, so no new imports are needed. + +No changes to the decorator, the back-stack controller, the runs model, tab resolution, or +deep-link gating. + +## Primary risk: visual collisions + +Behavior is safe (tab highlighting and the top-back-arrow + bottom-bar combination are +exactly what contract detail does today). The real risk is **layout**: the decorator hands +the destination the space *above* the bar and consumes the bottom system-bar inset. A +screen with a bottom-pinned CTA/button will now sit above the bar rather than at the screen +edge. Scrollable content is unaffected. + +Therefore this is a verify-in-app change, not a code-only change. + +## Verification + +Run the app and, for each of the 9 screens: +1. Confirm the correct tab is highlighted (Payments screens → Payments; Profile/Certificates + screens → Profile). +2. Confirm no bottom-anchored content (CTAs, buttons) collides with or is hidden behind the + bar. +3. Confirm tapping the owning tab pops that tab's run back to its root, and tapping a + different tab switches cleanly. +4. Confirm the top back-arrow still pops one entry as before. + +If any screen has a bottom CTA that collides, note it; the per-screen fix (or dropping that +screen from the batch) is decided during implementation. + +## Known tradeoff (accepted, not solved) + +Opt-in eligibility is scattered across feature modules with no central registry. Acceptable +for an experiment. Revisit (e.g. a key marker interface, or flipping to opt-out) only if the +eligible set grows large. + +## Out of scope + +- Runtime feature flag / A/B experiment plumbing. +- Flipping the default to opt-out. +- Any change to the manual-charge screens or to flow screens (claim, termination, document + generation). From 57bd7cd8a1d36bf49a18bb4367fd26fc619a9aa7 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Thu, 18 Jun 2026 12:30:50 +0200 Subject: [PATCH 2/3] feat(payments): show nav bar on payment info screens --- .../feature/payments/navigation/PaymentsEntries.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt index 38481c0a54..cdf95862d5 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt @@ -75,7 +75,7 @@ fun EntryProviderScope.paymentsEntries( ) } - entry { key -> + entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { key -> val memberChargeId = key.memberChargeId val viewModel: PaymentDetailsViewModel = assistedMetroViewModel { create(memberChargeId) } @@ -85,7 +85,7 @@ fun EntryProviderScope.paymentsEntries( ) } - entry { + entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { val viewModel: PaymentHistoryViewModel = metroViewModel() PaymentHistoryDestination( viewModel = viewModel, @@ -101,7 +101,7 @@ fun EntryProviderScope.paymentsEntries( ForeverDestination(viewModel = viewModel) } - entry { + entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { val viewModel: DiscountsViewModel = metroViewModel() DiscountsDestination( viewModel = viewModel, @@ -112,7 +112,7 @@ fun EntryProviderScope.paymentsEntries( ) } - entry { + entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { val viewModel: MemberPaymentDetailsViewModel = metroViewModel() MemberPaymentDetailsDestination( viewModel, From 018f4182d20d89eec032fdc1fd6e482db861c0da Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Thu, 18 Jun 2026 13:29:13 +0200 Subject: [PATCH 3/3] feat(profile): show nav bar on profile info screens --- .../android/feature/profile/tab/ProfileEntries.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/feature/feature-profile/src/main/kotlin/com/hedvig/android/feature/profile/tab/ProfileEntries.kt b/app/feature/feature-profile/src/main/kotlin/com/hedvig/android/feature/profile/tab/ProfileEntries.kt index 450149ec69..01edd18b32 100644 --- a/app/feature/feature-profile/src/main/kotlin/com/hedvig/android/feature/profile/tab/ProfileEntries.kt +++ b/app/feature/feature-profile/src/main/kotlin/com/hedvig/android/feature/profile/tab/ProfileEntries.kt @@ -91,7 +91,7 @@ fun EntryProviderScope.profileEntries( navigateUp = backstack::navigateUp, ) } - entry { + entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { val viewModel: ContactInfoViewModel = metroViewModel() ContactInfoDestination( viewModel = viewModel, @@ -100,7 +100,7 @@ fun EntryProviderScope.profileEntries( popBackstack = backstack::popBackstack, ) } - entry { + entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { val viewModel: AboutAppViewModel = metroViewModel() InformationDestination( viewModel = viewModel, @@ -114,12 +114,12 @@ fun EntryProviderScope.profileEntries( openUrl = openUrl, ) } - entry { + entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { LicensesDestination( onBackPressed = backstack::navigateUp, ) } - entry { + entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { val viewModel: CertificatesViewModel = metroViewModel() CertificatesDestination( viewModel = viewModel, @@ -128,7 +128,7 @@ fun EntryProviderScope.profileEntries( onNavigateToTravelCertificate = dropUnlessResumed { onNavigateToTravelCertificate() }, ) } - entry { + entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { val viewModel: SettingsViewModel = metroViewModel() SettingsDestination( viewModel = viewModel,