Restore driver-share "Add to RoadFlare" button (roadflared: scheme)#4
Draft
variablefate wants to merge 1 commit intomainfrom
Draft
Restore driver-share "Add to RoadFlare" button (roadflared: scheme)#4variablefate wants to merge 1 commit intomainfrom
variablefate wants to merge 1 commit intomainfrom
Conversation
variablefate
added a commit
that referenced
this pull request
Apr 20, 2026
The button set href to nostr:<npub>, but RoadFlare iOS does not register any URL scheme today, so tapping it never opened the app — best case it errored in Safari, worst case some unrelated Nostr client handled it. The copy button on Account ID and the QR code already provide the working paths for adding a driver/rider; the broken CTA only created confusion. A driver-only replacement using a future roadflared: scheme is drafted in #4 (pairs with variablefate/roadflare-ios#64). A rider-only replacement using roadflarer: is drafted in #5, waiting on a future iOS driver app. This commit clears the way for both by removing the placeholder element and its JS handler. Co-authored-by: variablefate <variablefate@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On /share/d/<npub>, render a primary CTA that deep-links into RoadFlare iOS via roadflared:<npub>?name=<display-name>. Button stays hidden on rider shares so this change only affects the driver-share flow. Blocked on: roadflare-ios registering the roadflared: URL scheme and handling incoming npubs (variablefate/roadflare-ios#64). Keep this PR as draft until the matching App Store build ships, then mark ready and merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4f6b163 to
394801e
Compare
variablefate
added a commit
to variablefate/roadflare-ios
that referenced
this pull request
Apr 28, 2026
#66) * feat(app): register roadflared: URL scheme for driver-share deep links Tap a roadflared:<npub>?name=... link on a device with RoadFlare installed → app opens, drivers tab is selected, AddDriverSheet is presented pre-filled with the parsed npub and display name. Replaces the previous nostr: scheme attempt, which the app never registered as a handler. Routing pattern: app-level .onOpenURL → AppState intent property → DriversTab observation. AppState gains pendingDriverDeepLink and handleIncomingURL(_:); DriversTab observes via .onChange + .task and hands the parsed payload to AddDriverSheet's new prefill: init param. Pattern is documented in ADR-0012; reuses DriverQRCodeParser via a new parseRoadflaredURI arm that mirrors the existing parseNostrURI arm. Also fixes a latent crash in parseNpubWithOptionalQuery: an empty body (e.g. parse("nostr:") or parse("roadflared:")) split to an empty array and crashed on parts[0]. Now guarded; both arms get regression tests. Closes #64. Once this ships in an App Store build, merge the matching site PR variablefate/roadflare-site#4 to restore the "Add to RoadFlare" button on /share/d/<npub> pages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * review: address code-review findings on PR #66 Three fixes from the code-review pass: 1. Cross-user state leak — `pendingDriverDeepLink` was missing from `AppState.prepareForIdentityReplacement`, so an unconsumed deep link could survive into the next user's session. Added the reset alongside the analogous `requestRideDriverPubkey` and `selectedTab` resets, plus a regression test that exercises the logout path end-to-end. 2. Sheet clobber on rapid second deep link — `DriversTab` previously used `.sheet(isPresented:)` with separate `showAddDriver` / `addDriverPrefill` state. SwiftUI does not re-evaluate `.sheet(isPresented:)`'s content closure when the bound state changes, so a second `roadflared:` URL arriving while the first sheet was still presented silently dropped. Refactored to `.sheet(item:)` with a private `Identifiable` wrapper (`AddDriverPresentation`); a new presentation identity now triggers SwiftUI to dismiss-and-re-present with the latest prefill. Also consolidated `.onChange` + `.task` into `.onChange(of:initial:)` to handle both warm and cold-start paths in one place. Net: 3 state vars become 1, two modifier blocks become one. 3. Misleading test name — `rejectsRoadflarerSchemeOnRiderApp` actually pinned the *acceptance* of `roadflarer:` via the embedded-npub fallback. Renamed to `acceptsRoadflarerSchemeViaEmbeddedNpubFallback` and tightened the comment. ADR-0012 updated to reflect the `.sheet(item:)` pattern and the `prepareForIdentityReplacement` cleanup contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: cold-start deep link survives onboarding + #70 sync-screen bug Two fixes folded together since both touch AppState's identity-creation paths. A. Cold-start regression I introduced in 55c8f83 — the unconditional `pendingDriverDeepLink = nil` in `prepareForIdentityReplacement` wiped a deep link that arrived before the user had any account. `generateNewKey`, `importKey`, AND the new `createWithPasskey` all call `prepareForIdentityReplacement` early in their flow, so a first-time user tapping a `roadflared:` link from elsewhere on iOS would lose the intent the moment they tapped "Generate Key" / "Create with Passkey" in the welcome screen. ADR-0012 explicitly says this case must work; the pass-1 fix broke it. Fix: gate the clear on `keypair != nil`. Cross-user-leak protection (the original Finding 1 from pass-1 review) still works because logout/identity-replacement is only meaningful when there IS a prior keypair to replace. Added regression test for the keypair == nil path. The keypair-set path can't be unit tested here — RoadFlareTests target lacks Keychain entitlement (-34018) — so it lives in the manual test plan. B. Issue #70 — passkey-create flow showed "Restoring Your Data" sync screen for brand-new accounts because `WelcomeView.createWithPasskey()` reused `importKey()`, which sets `authState = .syncing` while it queries relays for a non-existent prior identity. Fix: add a new `AppState.createWithPasskey(_ nsec:)` method mirroring `generateNewKey`'s structure (transitions directly to `.profileIncomplete`, no .syncing detour). Re-routed `WelcomeView.createWithPasskey()` through it. The "Log In with Passkey" recover-account flow still uses `importKey` since "Restoring Your Data" is correct copy there. ADR-0012 updated with the conditional clear nuance and a new "Onboarding interaction" subsection enumerating the three account- establishment paths and which auth-state route they take. Closes #70. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: also gate selectedTab/requestRideDriverPubkey on prior keypair Device test of cold-start preservation revealed the conditional clear in prepareForIdentityReplacement was incomplete: pendingDriverDeepLink was correctly preserved (per the prior fix), but `selectedTab = 0` was still unconditional. After completing onboarding, the user landed on the Ride tab instead of the Drivers tab, so the AddDriverSheet observer in DriversTab didn't fire until the user manually navigated there. Manually navigating to Drivers DID present the sheet — confirming the deep-link payload survived correctly, just that the tab routing was clobbered. Move all three navigation intents (selectedTab, requestRideDriverPubkey, pendingDriverDeepLink) inside the same keypair-conditional block: cleared on actual identity replacement (logout, key replacement when a prior keypair exists), preserved on first-time setup so cold-start state survives the user's first generateNewKey/createWithPasskey/importKey call. Test renamed to `navigationIntentSurvivesIdentityReplacementWhenNoKeypair` and now asserts both the pendingDriverDeepLink and selectedTab survive. ADR-0012 updated to call out the dual-state preservation explicitly (without selectedTab preservation, the user lands on the wrong tab and DriversTab — where the sheet observer lives — is not the first tab to mount post-`.ready`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: variablefate <variablefate@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2. Blocked on variablefate/roadflare-ios#64.
Summary
Adds a primary "Add to RoadFlare" CTA on
/share/d/<npub>driver-share pages that deep-links into RoadFlare iOS via the customroadflared:URL scheme. Only visible on driver shares — rider shares stay untouched (tracked separately in #3).What ships
404.html:<a id="add-driver-btn" class="btn btn-primary" hidden>Add to RoadFlare</a>placed inside.actions, above.store-buttons.renderSharePage, whenisDriver === true: setsadd-driver-btn.href = 'roadflared:' + npub + (name ? '?name=' + encodeURIComponent(name) : '')and unhides it.Why it's a draft
This button goes to a
roadflared:URL. RoadFlare iOS does NOT register that scheme yet. Tapping it before the iOS update ships produces the same "Safari cannot open" error we just removed. Do not mark this PR ready until aroadflare-iosApp Store build with #64 resolved is live.Interaction with the removal PR
This PR is designed to be additive — it introduces new elements / new code paths without touching the old
add-btnor itsnostr:JS line. The separate removal PR (#TBD) deletes those. The two are non-conflicting regardless of merge order.Test plan (when ready)
roadflared:support is livegh pr view <n>→ rebase ontomain(which no longer hasadd-btn)/share/d/<real-npub>→ button visible, href isroadflared:npub1...?name=...with the display name URL-encoded correctly/share/r/<npub>→ button is NOT visible (rider share)🤖 Generated with Claude Code