feat(frontend): measure whats-new video engagement in posthog - #989
Merged
Conversation
Events for the release video modal: opened (auto/manual trigger), started, progress milestones, completed, and a close summary with watch seconds and percent. Playback state comes from the embed's own postMessage stream (enablejsapi=1 plus a listening handshake), so no YouTube script loads, the CSP stays untouched, and the frame stays on the nocookie origin. Milestones fire as crossed so a closed tab still leaves a record; pagehide flushes the summary. Login recency is a PostHog query-side join, not a client guess.
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.
What is this change?
PostHog analytics for the "What's new" release video modal, answering: did they watch, how long, in which language, and how the showing relates to login recency.
Events
whats_new_modal_openedtrigger(auto/manual),seconds_since_page_loadwhats_new_video_startedseconds_after_openwhats_new_video_progressmilestone_percentwhats_new_video_completedvideo_watched_seconds,video_duration_secondswhats_new_modal_closedreason,video_watched,video_watched_seconds,video_duration_seconds,video_percent_watched,video_max_percent,modal_open_secondsEvery event carries
language(the UI language),version(the release entry) andtrigger(the automatic showing vs the sidebar's "What's new").How playback is observed
The embed runs on www.youtube-nocookie.com, the only YouTube origin
frame-srcallows, andscript-srcallows no YouTube origin at all. So the official IFrame API loader (a script served from www.youtube.com) is not an option: the CSP would block it in production and every metric would silently read zero. Instead the iframe URL gainsenablejsapi=1plusorigin, and the modal speaks the player's own postMessage protocol directly: alisteninghandshake, theninfoDeliverymessages carryingcurrentTime,durationandplayerState. No script loads, no CSP change, and the nocookie privacy posture stays as it was.Watch time is the sum of small forward
currentTimesteps: a seek does not count as watching, double speed counts the content covered, rewatching counts again but the percentage caps at 100.video_max_percentis the furthest position reached. Milestones fire the moment they are crossed, so a tab closed mid-video still leaves a durable record;pagehideflushes the close summary for the walk-away case (posthog-js flushes its queue with sendBeacon there).Time since login
Deliberately not stamped from the client. A client-side breadcrumb (localStorage written at login) fabricates the answer for every already-logged-in user, misses sessions restored from a refresh token, and dies with cleared site data. The honest source already exists: PostHog holds each person's event history, including
user_logged_in. Recency is a query-side join, for example (HogQL):Swap the
user_logged_infilter for any prior event with a 30 minute guard to measure time since last activity instead, since sessions persist across days and form logins are rarer than visits.Questions this schema answers
whats_new_modal_openedtowhats_new_video_startedfunnel.trigger=manualreopens and replays vs the automatic showing.modal_open_secondswithvideo_watched=false.Supersedes #986: same goal, but that branch loads the IFrame API script that
script-srcblocks (metrics would silently read zero in production), stores last-login in localStorage (the self-heal fabricates it for every existing user), and reports watch data only on close (a closed tab loses the whole record).Test plan
vitest run src/components/release: 49 tests pass, including new tracker unit tests and modal analytics tests that hand-deliver widget messages (origin and source checks, milestone dedupe, summary-once semantics).biome lint,tscandvite buildare clean; lingui catalogs re-extracted (line-reference shifts only, no new strings).useChunkAnchorScroll.test.tsxreproduce on a clean main checkout; unrelated and untouched here.