feat(native): make the deferred-link match rate measurable - #2587
feat(native): make the deferred-link match rate measurable#2587innolope-dev wants to merge 2 commits into
Conversation
The restore path had no telemetry, and its clipboard catch swallowed the one distinction that matters: a declined iOS paste prompt and an organic install both end up as `raw === null`. A hand-off that never works would therefore look exactly like one nobody used, and the download modal (TASK-20769) is about to put real volume through it. Split the empty cases into distinct outcomes — clipboard_unavailable, no_handoff, marker_missing — and emit them with the channel alongside which fields actually came back. Only booleans and the channel are sent; the invite code, campaign tag and destination never leave the device (asserted in a test). Capture is fully guarded: this runs on first launch, where a telemetry failure must not cost the user their restored context. trackDeferredHandoffCreated is the seam for the modal to report the denominator — deliberately not fired inside playStoreUrlWithReferrer, which is a pure builder invoked on render and would count impressions.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
kushagrasarathe
left a comment
There was a problem hiding this comment.
Reviewed — the direction is right and I'd like to keep this PR (don't close it). Matches the repo's direct posthog.capture convention (useZeroDev, webauthn.utils), booleans-only payload is exactly the right privacy line, and the outcome taxonomy (no_handoff / marker_missing / clipboard_unavailable) is the diagnostic we'd otherwise wish we had after a silent iOS rollout. The posthog-throw isolation test is appreciated.
Two things before this can land, both fallout from the base branch moving under you today:
1. Stale base — the block you instrument was rewritten in ffc9e79e8 (Jota's review fixes). Your diff context still shows the old "one shot, even on failure" consumption. On the current base: Android only consumes on a definitive read (transient null retries next launch), and iOS consumes before the prompt-raising read. Needs a rebase onto current feat/deferred-deep-link-main.
2. After that rebase, the Android transient path will fire no_handoff on every retry launch — one install with a flaky first boot emits N events and inflates no_handoff, which skews the exact match-rate metric this PR exists to protect. Suggest a distinct read_failed outcome for the android transient-null path (it's also a signal worth seeing on its own — a rising read_failed means the 5s timeout is too tight), or alternatively only capture once consumption happens.
One non-blocking check: on the very first cold launch, capture may run before posthog.init — posthog-js buffers pre-init calls on the singleton so it should be fine, but worth one on-device confirmation via /dev/deferred when we run the internal-track test.
Plan-wise: #2560 merges first, then retarget this to main, rebase, adapt per above. Happy to do the rebase myself if you prefer — say the word.
…telemetry branch #2560's review pass replaced the unconditional one-shot burn with markConsumed(): a transient android referrer read no longer consumes and retries on the next launch. Fold the telemetry into that: track whether the one-shot was actually burned and skip the empty-outcome capture when it wasn't — an unconsumed restore is a pending retry, not an organic install, and firing there would double-count the install and file a broken read as organic. The android-null test moves to the iOS empty-clipboard case, which is what an organic install now looks like on the reporting side; a new test pins the silent-retry behaviour. Merges the base branch rather than main: main carries unsigned content-submodule commits that the signature ruleset re-flags on a feature ref (GH013).
|
Conflict resolved + retargeted to #2560 merged, so the old base ( The conflict wasn't purely textual. #2560's review pass replaced the unconditional one-shot burn with
|
Summary
Follow-up to #2560, stacked on
feat/deferred-deep-link-main. Adds the telemetry the deferred-link restore path is missing.feat/deferred-deep-link-main, so the diff shown includes #2560's commits until that merges. Retarget tomainonce #2560 lands — this branch's own change is 3 files / +157.Why
The restore path currently emits nothing, and the iOS clipboard
catch {}swallows the one distinction that matters:A declined paste prompt and an organic install both end up as
raw === null. So a hand-off that never works on iOS is indistinguishable from one nobody used — and the only signal either way is the absence of restores, which looks the same as success. The download modal (TASK-20769) is about to drive real volume through this, and the plan was always to measure the match rate before escalating to a blocking interstitial.What
DEFERRED_LINK_RESTORED, fired once per fresh install withchannel(referrer/clipboard/none) and one of four outcomes:restoredno_handoffmarker_missingpnutdl=1— organic Play referrer, or unrelated clipboard text that passed the URL gateclipboard_unavailableOn
restoredit also reportshas_dest/has_locale/has_invite/has_campaign.Plus
trackDeferredHandoffCreated(platform)as the seam for the modal to report the denominator. Deliberately not fired insideplayStoreUrlWithReferrer— that's a pure URL builder invoked on render, so it would count impressions as taps.Privacy
Only booleans and the channel leave the device. The invite code, campaign tag and destination never do — putting a user's inviter and intended screen into analytics would be a real regression. There's a test asserting the payload values are absent from the captured events.
Safety
posthog.captureis wrapped intry {} catch {}at both call sites. This runs on the first-launch path, where a telemetry failure must not cost the user their restored context — covered by a test that makes capture throw and asserts the restore still returns.Tests
4 added to the existing suite (25 pass total, including all 21 from #2560 unchanged):
marker_missing, notno_handofftsc --noEmitclean on the touched files; prettier clean.