Skip to content

Instrument the Toronto voter survey for PostHog - #80

Merged
mikaalnaik merged 1 commit into
mainfrom
mikaal/toronto-survey-analytics
Sep 8, 2026
Merged

Instrument the Toronto voter survey for PostHog#80
mikaalnaik merged 1 commit into
mainfrom
mikaal/toronto-survey-analytics

Conversation

@mikaalnaik

Copy link
Copy Markdown
Contributor

Completion and stage drop-off for the Toronto voter survey were both invisible: the only event the survey sent was survey_submitted, which says how many finished and nothing about how many started or where the rest went.

Adds useSurveyAnalytics (src/app/toronto/vote/2026/survey/analytics.ts), which holds every event in one place so each carries the same identity — survey, survey_version, election, step_count — plus seconds_elapsed.

Events

EventFires whenKey props
survey_viewedquestionnaire mounts
survey_startedfirst non-empty answer (once per run)step props
survey_step_completeda step validates and they move on, including the last step on submitstep_index, step_id, answered_on_step
survey_step_blockedvalidation holds them on the stepfields, field_count
survey_step_backBack pressedstep props
survey_submittedAPI accepted the responseward
survey_submit_failedsubmit threwstep props
survey_abandonedtab hidden or unloaded mid-runlast-seen step props
survey_results_viewedcomparison resolves after completionhas_comparison, ward, races

How to read it in PostHog

  • Completion:survey_viewedsurvey_startedsurvey_submitted.
  • Stage drop-off: a funnel of survey_step_completed repeated per step, filtered on step_index 0, 1, 2… It fires on clearing a step rather than on being shown one, so someone who lands on step 3 and leaves never emits it — that is the drop.
  • survey_step_blocked separates "couldn't get past validation" from "gave up" on the same step.
  • survey_results_viewed separates completions shown a real candidate comparison from wards whose candidates have not answered.

Notes for review

  • survey_submitted moves out of submitSurvey.ts into the hook, keeping its name and existing properties, so one place emits it with the step and timing properties the funnel is built on. posthog.identify stays in submitSurvey, where the email is known to have been accepted.
  • survey_abandoned is a best-effort last-seen marker, not a verdict: someone who backgrounds the tab and returns to finish emits both it and survey_submitted. It is capped at one per run and sent via sendBeacon. Read completion from survey_submitted, not as the inverse of this.

Testing

tsc --noEmit and eslint pass. The events themselves are not verified against a live project — there is no NEXT_PUBLIC_POSTHOG_TOKEN locally, so posthog.init never runs and captures are no-ops in dev. Worth a look at the PostHog live event feed on the preview deploy before relying on the funnel.

🤖 Generated with Claude Code

Completion and stage drop-off were both invisible: the only event the
survey sent was `survey_submitted`, which says how many finished and
nothing about how many started or where the rest went.
Adds useSurveyAnalytics, which holds every event in one place so they
carry the same identity — survey, version, election — plus elapsed
seconds. A funnel is only as good as that consistency: a step event
missing survey_version pools answers to two question sets into one
drop-off number.
`survey_step_completed` fires on clearing a step rather than on being
shown one, so a respondent who lands on a step and leaves emits nothing
for it — which is the drop we want counted. Repeat it as successive
funnel steps filtered on step_index to read stage drop-off.
`survey_step_blocked` separates "couldn't get past validation" from
"gave up", and `survey_results_viewed` separates completions shown a
real candidate comparison from wards with no published answers.
`survey_submitted` moves out of submitSurvey into the hook, keeping its
name and properties, so it carries the step and timing properties the
completion funnel is built on. posthog.identify stays where the email is
known to have been accepted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

Copy link
Copy Markdown

Greptile Summary

The PR centralizes Toronto voter-survey analytics in a new hook and instruments views, starts, step transitions, submission outcomes, abandonment, and result rendering.

  • Adds consistent survey identity, progress, and elapsed-time properties to PostHog events.
  • Moves successful submission capture into the analytics hook while retaining accepted-email identification in submitSurvey.
  • Tracks result availability after candidate comparison data resolves.

Confidence Score: 5/5

The PR appears safe to merge; no concrete blocking or independently actionable non-blocking defect remains.

The instrumentation follows the existing survey lifecycle, successful submissions are captured after API acceptance, and result reporting waits for the comparison data state that controls what the voter sees.

Important Files Changed

FilenameOverview
src/app/toronto/vote/2026/survey/SurveyClient.tsxIntegrates lifecycle, step, submission, and comparison-result analytics without changing the survey’s user-facing control flow.
src/app/toronto/vote/2026/survey/analytics.tsIntroduces a centralized PostHog hook with consistent event properties and documented best-effort abandonment semantics.
src/app/toronto/vote/2026/survey/submitSurvey.tsRemoves duplicate submission capture while preserving PostHog identification after the API accepts the response.

Sequence Diagram

sequenceDiagram
participant V as Voter
participant S as SurveyClient
participant A as Survey analytics
participant API as Survey API
participant PH as PostHog
V->>S: Open questionnaire
S->>A: Mount
A->>PH: survey_viewed
V->>S: Enter first answer
S->>A: started(progress)
A->>PH: survey_started
V->>S: Complete or fail validation
S->>A: stepCompleted / stepBlocked
A->>PH: Step event
V->>S: Submit
S->>API: submitSurvey
API-->>S: Accepted response
S->>A: submitted(progress, response)
A->>PH: survey_submitted
S->>A: resultsViewed(comparison)
A->>PH: survey_results_viewed
Loading

Reviews (1): Last reviewed commit: "Instrument the Toronto voter survey for ..." | Re-trigger Greptile

@mikaalnaik
mikaalnaik merged commit 8ae5f82 into mainSep 8, 2026
2 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@mikaalnaik