From d1ac41d8ef783ff6a2fe0b2a4560acb5baae3380 Mon Sep 17 00:00:00 2001 From: Cyborg Viktor <307551610+vpetersson-bot@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:09:45 +0000 Subject: [PATCH] feat(analytics): name the app so the first page view is attributed `playerProfileResponse` now takes the app name, which adds a ready-made `userProperties` object to the `/api/player` payload. The inline bootstrap sets it before `gtag('config')` fires the automatic `page_view`. Without it that page view goes out ahead of `trackPlayer`, which does not run until after DOMContentLoaded, so it carries no player fields. Where the client_id survives the loss is invisible, because every later page view is attributed. Where the id is minted fresh on every load, no page view is ever attributed: `player_vendor=yodeck` measured 150 users and zero page views on 2026-08-14, and 21% of app runs fleet-wide carried no player fields. Kit bumped to 2026.8.9, which adds the option. Co-Authored-By: Claude Opus 5 (1M context) --- bun.lock | 4 ++-- package.json | 2 +- src/index.tsx | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index 080c5a8..76d0ca7 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "screenly-rss-reader", "dependencies": { - "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#2026.8.8", + "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#2026.8.9", "hono": "^4.12.33", "qrcode-generator": "^2.0.4", }, @@ -191,7 +191,7 @@ "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], - "@screenly-labs/signage-kit": ["@screenly-labs/signage-kit@github:Screenly-Labs/signage-kit#85934fa", { "dependencies": { "@fontsource-variable/bricolage-grotesque": "^5.3.0", "@fontsource-variable/fraunces": "^5.3.0", "@fontsource-variable/hanken-grotesk": "^5.3.0", "@fontsource-variable/jetbrains-mono": "^5.3.0", "@fontsource-variable/newsreader": "^5.3.0", "@fontsource/space-mono": "^5.3.0" }, "peerDependencies": { "@csstools/postcss-cascade-layers": "^6", "browserslist": "^4", "esbuild": "^0.28", "lightningcss": "^1.32", "postcss": "^8" }, "optionalPeers": ["@csstools/postcss-cascade-layers", "postcss"] }, "Screenly-Labs-signage-kit-85934fa", "sha512-RcJ7vWlJWT/TFu5YdrAQAbqaiHnqobWRk2XwaC0kW1X+bCzJoDWT9kS/pMDxGEeNEixMZWtEG/vjBdg/x0fIOw=="], + "@screenly-labs/signage-kit": ["@screenly-labs/signage-kit@github:Screenly-Labs/signage-kit#563cbb9", { "dependencies": { "@fontsource-variable/bricolage-grotesque": "^5.3.0", "@fontsource-variable/fraunces": "^5.3.0", "@fontsource-variable/hanken-grotesk": "^5.3.0", "@fontsource-variable/jetbrains-mono": "^5.3.0", "@fontsource-variable/newsreader": "^5.3.0", "@fontsource/space-mono": "^5.3.0" }, "peerDependencies": { "@csstools/postcss-cascade-layers": "^6", "browserslist": "^4", "esbuild": "^0.28", "lightningcss": "^1.32", "postcss": "^8" }, "optionalPeers": ["@csstools/postcss-cascade-layers", "postcss"] }, "Screenly-Labs-signage-kit-563cbb9", "sha512-DU6Bd5sOs8m396OLoUrVfte90d+I2mNuOUvwxCk3LS4jwR3kU18vMJ30aCH32/GcO+9hJ6i0vLcSPtcrr/Pmvw=="], "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], diff --git a/package.json b/package.json index 847c800..0728009 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "license": "AGPL-3.0-only", "dependencies": { - "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#2026.8.8", + "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#2026.8.9", "hono": "^4.12.33", "qrcode-generator": "^2.0.4" }, diff --git a/src/index.tsx b/src/index.tsx index 0aa2820..561bb2c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -130,6 +130,11 @@ app.route('/img', img) // (yodeck, pisignage, xogo, iadea and friends) that header is the only thing that names // the vendor. If this response were ever cached, every screen would inherit whichever // player happened to warm it and the census would silently collapse onto one vendor. -app.get(PLAYER_PROFILE_PATH, (c) => playerProfileResponse(c.req.raw)) +// +// Naming the app here is what gets the page view attributed: the payload then carries +// ready-made user properties, which the inline bootstrap sets BEFORE gtag('config') fires +// the automatic page_view. Without it the first page view under any client_id has no player +// fields, and a screen that mints a fresh id every load never attributes one at all. +app.get(PLAYER_PROFILE_PATH, (c) => playerProfileResponse(c.req.raw, { app: 'rss-reader' })) export default app