Follow-up from #5609 / PR #5615.
That PR made a browser-denied notification permission move the activation wizard's alerts step into its blocked state. Because a blocked step resolves as skipped (same as a step blocked at mount), the denial would have become byte-identical to a voluntary skip in the record. PR #5615 closed that at the event level by adding pro-activation-step-blocked.
The durable side is still collapsed. ProActivationOutcomeSnapshot (src/services/billing.ts) buckets into confirmedSteps / skippedSteps / failedSteps only, and ActivationStepOutcome (src/services/pro-activation-state.ts) has no blocked member. So:
- A real-time listener on the Umami event stream can tell "browser denied" from "user skipped".
- Anything querying persisted per-account state in Convex after the fact cannot — a blocked step is in
skippedSteps with no marker.
That matters for the questions this data is actually for: sizing the push-permission-denial population, and deciding whether re-prompting an account is worth anything (it never is, once permission is denied — the browser will not ask again).
Shape
Add a fourth bucket rather than widening an existing one, so behavioral consumers are untouched:
ActivationStepOutcome gains 'blocked'; the shell writes it on the advance-skip arm when blockedByConfirm.has(step.id).
buildActivationOutcomeBuckets gains blockedSteps, plumbed through recordProActivationOutcome and the Convex validator.
buildExitSummary must keep rendering it as pending, not failed — the summary says "we couldn't set this up" for failed, which is wrong for a browser refusal we never attempted.
selectStepEvent should keep emitting stepBlocked (already wired) rather than stepSkipped for the new outcome.
Note this is a server-contract change: the Convex validator and any existing rows need to tolerate the new bucket, so it wants its own PR rather than riding along with a UX fix.
Why it was split out
Raised during PR #5615's review as an explicit "observation, not a blocker". The event-level fix already covers funnel analysis and live monitoring, which is the majority of the value; this is the remaining after-the-fact-query gap.
Follow-up from #5609 / PR #5615.
That PR made a browser-denied notification permission move the activation wizard's alerts step into its blocked state. Because a blocked step resolves as
skipped(same as a step blocked at mount), the denial would have become byte-identical to a voluntary skip in the record. PR #5615 closed that at the event level by addingpro-activation-step-blocked.The durable side is still collapsed.
ProActivationOutcomeSnapshot(src/services/billing.ts) buckets intoconfirmedSteps/skippedSteps/failedStepsonly, andActivationStepOutcome(src/services/pro-activation-state.ts) has noblockedmember. So:skippedStepswith no marker.That matters for the questions this data is actually for: sizing the push-permission-denial population, and deciding whether re-prompting an account is worth anything (it never is, once permission is
denied— the browser will not ask again).Shape
Add a fourth bucket rather than widening an existing one, so behavioral consumers are untouched:
ActivationStepOutcomegains'blocked'; the shell writes it on theadvance-skiparm whenblockedByConfirm.has(step.id).buildActivationOutcomeBucketsgainsblockedSteps, plumbed throughrecordProActivationOutcomeand the Convex validator.buildExitSummarymust keep rendering it aspending, notfailed— the summary says "we couldn't set this up" forfailed, which is wrong for a browser refusal we never attempted.selectStepEventshould keep emittingstepBlocked(already wired) rather thanstepSkippedfor the new outcome.Note this is a server-contract change: the Convex validator and any existing rows need to tolerate the new bucket, so it wants its own PR rather than riding along with a UX fix.
Why it was split out
Raised during PR #5615's review as an explicit "observation, not a blocker". The event-level fix already covers funnel analysis and live monitoring, which is the majority of the value; this is the remaining after-the-fact-query gap.