Release: report the detected player to GA4 - #33
Merged
Merged
Conversation
The stale-player notice already profiles every viewer to decide whether to warn it, but that judgement was made and thrown away on each load — so the population we are warning, and whether it ever shrinks, was unmeasurable. Send the same profile as a `player_detected` event. One detectPlayer() call now feeds both consumers, so the notice and the telemetry can never disagree about what a screen is running. Client-side for the same reason the notice is: the SSR page cache is keyed by asset version + country + timezone with no user-agent component, so a server -side profile would describe whichever player missed the cache and then be attributed to every hit behind it. Params are prefixed `player_` because the GA property is shared with the other Screenly-Labs apps. Three calls worth naming: * Nulls are sent as "unknown" rather than omitted — an absent param reads as "(not set)" in GA4, indistinguishable from a dimension nobody registered. * engine_version stays numeric so GA4 will aggregate it. * player_stale is sent despite being derivable from vendor + engine: "stale" is a judgement this app makes (see stale-player.js) rather than something a report author should re-derive, and it is what makes notice impressions countable against upgrades. `sources` is not sent — page JS can only read the UA and referrer, so it is near-constant here and would spend a custom dimension on nothing. Verified against the built bundle in a browser across four spoofed player UAs: one event per load, player_stale agreeing with whether the notice mounted, and a silent no-op when gtag is absent (dev, or a blocked tag in the field). Note: the params stay invisible in reports until they are registered as custom definitions in the GA4 UI — engine_version as a metric, the rest dimensions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Report the detected player to GA4
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.
Promotes #32 to production. Production is exactly two commits behind, so this release is that one change and nothing else.
Sends a
player_detectedGA4 event carrying the player profile the stale-notice already computes — vendor, platform, model, category, engine + version, below-floor, confidence, and the app's ownplayer_staleverdict.Tested on stage
Stage (
stage-clock.srly.io) serves the new bundle and I drove it in a real browser across four spoofed player UAs. Each queued exactly one well-formed event to the real gtag shim, withplayer_staleagreeing with whether the notice actually mounted:unknowntrueanthiasfalsescreenlyfalsebrightsignfalseplayer_modelcorrectly picked upXT1144off the BrightSign UA. No page errors anywhere; the clock renders throughout.What I could NOT verify, and who should
googletagmanager.comis connection-refused from the environment I tested in, so realgtag.jsnever loaded there and nothing flushed to Google's collect endpoint. Everything up to and including the handoff to gtag is confirmed; GA's actual ingestion is not. Worth a look in GA4 DebugView/Realtime from an unblocked network — on stage before this merges, or on production right after.Before this reports anything
The params stay invisible in reports until registered as custom definitions in the GA4 UI (Admin → Custom definitions) on the production property
G-QD11F7ZZ1B—player_engine_versionas a metric, the rest as dimensions. Merging without that still collects the data; it just won't be queryable until they're registered.🤖 Generated with Claude Code