feat: add deep links to every screen - #1119
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Thanks for addressing the existing backup and hardware findings; I confirmed both fixes on 511250c.
I found three blocking issues: cold-start links bypass the dev-mode gate, bitkit://screen/recovery-mode is consumed by the legacy parser before the gate, and external-confirm can reach invalid fresh state and crash. I also left two non-blocking corrections for Home registration and the adb -W guidance.
There was a problem hiding this comment.
Thanks for addressing the earlier findings. Three blocking issues remain:
- Cold-start coverage is missing at the Activity/
NavHostboundary. - Late transfer destinations allow entry without the flow state they require.
- Root-screen links route behind an active sheet.
ovitrif
left a comment
There was a problem hiding this comment.
One blocking navigation regression remains: rejected screen links dismiss an active sheet before route handling determines that the URI is invalid.
There was a problem hiding this comment.
The deep-link implementation currently splits navigation policy across the route models and parallel registries, so I came up with 4 required corrections to keep direct entry safe and make the route hierarchy the single source of truth, and fix the changelog:
- A denied root-screen link dismisses an active sheet before rejection, so rejected input changes the visible flow.
- Root-screen eligibility has a second owner in
ScreenDeepLinks.DENIED, so new routes become reachable without an explicit decision and every route change must synchronize a parallel policy. SheetDeepLinks.SHEETScorrectly fails closed while duplicating which nested states may start a flow outside the sealed route families that define them.- The changelog is incorrect because it describes an internal QA workflow rather than the public developer capability available to people running their own builds.
# Conflicts: # app/src/main/java/to/bitkit/ui/ContentView.kt
|
The red e2e-status is not from this branch. e2e-tests-staging - pubky_paykit fails the same four contact specs on ContactViewName here and on #1123, which touches only channel-details time formatting. My #1111 passed that job on 29 Jul and ContactDetailScreen has not changed since May, so the break is on master or in the staging env rather than in either PR. The rest is green: build, build-local, build-staging, detekt, and both local e2e specs. |
There was a problem hiding this comment.
Thanks for addressing the earlier findings. One blocking issue and one docs cleanup remain:
ExternalConnectionis stillRoutes.DeepLinkablebut registered withcomposableWithDefaultTransitions, sobitkit://screen/external-connectionlost its Nav deep link after that helper stopped auto-registering links.docs/deeplinks.mdno longer matches the fail-closed route-owned model and should be removed rather than kept as a second source of truth for this developer-oriented feature.
PS. We can ignore the red e2e failures, it's caused by a premature merge of a PR in the e2e repo targeting paykit-related PRs that are not yet into master. Classic PR stacks issues 🙃 .
ovitrif
left a comment
There was a problem hiding this comment.
The ExternalConnection deep link is restored and docs/deeplinks.md is removed; this looks good to merge.
Fixes #659
Refs #1118
Refs #1126
This PR adds a
bitkit://screen/...URI for every screen in the app, bottom sheets included, gated on dev mode.Description
A destination declares whether it may be entered directly, in the model that defines it. 88 root routes are
Routes.DeepLinkableand 15 areRoutes.InternalOnly, anddeepLinkableComposableaccepts only the former, so a new screen has to make the choice rather than inherit one.ScreenDeepLinksadapts the URI and nothing else: it derives the id by kebab-casing the class name, soRoutes.RgsServeris reachable atbitkit://screen/rgs-server. Arguments without a default become path segments, arguments with a default become query parameters.Bottom sheets needed a second mechanism. They are not in the root graph: they are
Sheetvalues rendered bySheetHost, each carrying the start route of its own nestedNavHost, soNavController.handleDeepLinkcannot reach them. Each sealed route family declaresDeepLinkStartorInternalOnlyon its states and owns afromDeepLinklookup, andSheetDeepLinkspicks the family and wraps the result. Ids derive from class names throughout, sobitkit://screen/widgets/price-editcomes fromSheet.WidgetsplusWidgetsRoute.PriceEdit.send,receive,backup,widgets,hardwareand the three sheets with no nested graph.isDevModeEnabledinAppViewModel.handleDeeplinkIntent, and holds one until the wallet is loaded;ContentViewreplays it.MainActivityonce the gated pipeline has read it, so graph creation cannot navigate outside the dev-mode gate.bitcoin:,lightning:,lnurl*) on the scanner decode path, untouched.docs/deeplinks.md. The route markers,deepLinkableComposable, the sheet families'fromDeepLinkand the unit tests are the source of truth, and a parallel doc drifted three times during review.Not every nested state is a valid start destination. I fired all 51 against an emulator, then re-ran each rejected one in isolation against logcat. The states left internal fall into three groups:
SendRoute.FeeRateandFeeCustomsit insidenavigationWithDefaultTransitions<SendRoute.FeeNav>. A nested graph's child cannot be aNavHoststart destination, so both throwIllegalStateException: Cannot find startDestination ... from NavGraph.send/fee-navresolves but renders only the screen title.send/quick-paythrows onrequireNotNull(quickPayData)(SendSheet.kt:309).send/confirmand the four receive confirm/liquidity routes do not crash but render empty or zero-amount screens.send/confirmoffers "Swipe To Pay" over a payment that was never built.backup/successreports a backup that never ran, and its OK button persistsbackupVerified = true(BackupNavSheetViewModel.onSuccessContinue).backup/warningis one tap upstream of the same write.hardware/searchingwaits forever because discovery starts from the intro's continue action, andhardware/pairedclaims a paired device over default state.Tests pin the classification, and a final sweep confirmed the rejected paths no-op with the wallet overview intact and zero fatal exceptions.
Preview
N/A
QA Notes
Dev mode is on by default on debug builds (Settings ▸ Advanced ▸ Dev Settings). The app must be past onboarding.
Manual Tests
adb shell am start -a android.intent.action.VIEW -d "bitkit://screen/settings" to.bitkit.dev→ Settings opens. A mistyped id still resolvesMainActivity, because the manifest accepts everybitkit:URI by scheme, so check logcat forUnhandled screen deeplinkrather than relying on-W.bitkit://screen/send→ Send sheet on the recipient picker.bitkit://screen/widgets/price-edit→ Bitcoin Price editor.bitkit://screen/recovery-mnemonicandbitkit://screen/backup/show-mnemonic→ screen unchanged, recovery phrase never shown, logcat carriesUnhandled screen deeplink.bitkit://screen/send/fee-rateandbitkit://screen/backup/success→ screen unchanged, no crash, andbackupVerifiedis untouched.bitkit://screen/...link is ignored, on warm start and on cold start. Settings ▸ Support, tap Version five times to toggle.regression:scan abitcoin:/lightning:/lnurlURI → still decodes through the scanner path.Automated Checks
ScreenDeepLinksTest.kt: id derivation, path vs query argument placement, and that every route declares its eligibility.SheetDeepLinksTest.kt: bare-id defaults, sub-route selection, case-insensitive lookup, ids derived from theSheetclass names, and that every state marked as a start is registered in its family lookup.ScreenDeepLinkDetachmentTest: graph creation stays on Home after detachment, the captured URI reaches Settings only through the replay, and a denied route is not matched.journeys/deeplinks/, including cold-start cases for dev mode off and on.just compile,just test,just lintall pass, no new detekt findings.