Skip to content

feat(analytics): instrument official templates with PostHog - #7

Merged
xiongxz merged 4 commits into
mainfrom
codex/posthog-official-templates
Sep 4, 2026
Merged

feat(analytics): instrument official templates with PostHog#7
xiongxz merged 4 commits into
mainfrom
codex/posthog-official-templates

Conversation

@xiongxz

@xiongxz xiongxz commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • initialize PostHog in all 14 templates from platform-injected runtime config
  • automatically capture page views, page leaves, and performance; provide semantic conversion helpers for templates to wire at successful business operations
  • apply privacy-safe defaults: no autocapture, masked inputs and personal properties, URL/property sanitization, and stable per-session replay sampling
  • retry Next.js initialization after runtime config is available
  • add an analytics contract validator to CI

Validation

  • analytics contract validated for all 14 templates
  • registry test suite: 17 passed
  • production builds passed for representative Next.js and Vite templates, including todo, booking, ai-pdf-chatbot, admin-dashboard, and insight-flow-agent-chat
  • git diff --check

Companion PRs

  • lexmount/insforge-platform#81
  • lexmount/lex-insforge#4

@xiongxz

xiongxz commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Review(首轮,接替已关闭的 InsForge#60

和 insforge-platform#81 / lex-insforge#4 一起看的。InsForge#60 的"夹带 fork 私货"问题在这里不存在(只剩两个分析 commit,93 个文件),其余结论平移过来,并补了两个构建产物层面的实测。

本地验证:node scripts/check-analytics.mjs 14 个模板通过;scripts vitest 17/17;自己构建了 admin-dashboard(Vite)和 todo(Next output: export)看产物。

跨仓契约window.__INSFORGE_RUNTIME_CONFIG__、六个字段名、/.well-known/insforge-runtime-config.js 路径与 #81 的 webjob/layer.go 三方一致 ✅


P1 Next 模板里运行时配置脚本会排在 Next 自身的 async chunk 之后,热缓存时分析静默不上报

todo 静态导出的 out/index.html<head> 内脚本顺序(原样):

<script src="/_next/static/chunks/4bd1b696-….js" async="">
<script src="/_next/static/chunks/9da6db1e-….js" async="">
<script src="/_next/static/chunks/255-….js" async="">
<script src="/_next/static/chunks/main-app-….js" async="">
<script src="/_next/static/chunks/514-….js" async="">
<script src="/_next/static/chunks/app/page-….js" async="">
<script src="/.well-known/insforge-runtime-config.js">
<script src="/_next/static/chunks/polyfills-….js" noModule="">
</head>

源码里 <head><script src="/.well-known/…"/></head> 写在最前,但 Next/React 19 把自己的 bootstrap chunk 以 async 提到前面。async 脚本不按文档顺序执行,只要下载完就跑;Next 的 chunk 是不可变强缓存,第二次访问起基本秒到,而 /.well-known/insforge-runtime-config.js 是每次发布都变的东西。于是 instrumentation-client.ts(随 main-app chunk 执行)经常先于配置脚本跑:window.__INSFORGE_RUNTIME_CONFIG__ 还是 undefined → token 为空 → initializeAnalytics() 第一行就 return,且没有任何重试。结果是 9 个 Next 模板在真实使用中一部分页面加载零上报,还没有任何报错。

check-analytics.mjs 里 "runtime config must load in head before hydration" 只看源码顺序,检不出这个。

建议(两条都做):

  • initializeAnalytics() 不要假设顺序:全局不存在且 document.readyState === 'loading' 时挂 DOMContentLoaded 再试一次(同步脚本在 DOMContentLoaded 前一定已执行)。或者改用 next/scriptstrategy="beforeInteractive",它才是 Next 提供的"排在框架代码前面"的正规通道。
  • 契约脚本改成校验构建产物里的顺序(out/index.html / dist/index.html),或至少把这条断言的措辞降级。

顺带说 Vite 侧:dist/index.html 里入口被提升到 <head><script type="module">,配置脚本留在 <body>,文本顺序也是反的,但 module 脚本天然 defer,所以运行时没问题。同一条断言在 Vite 产物上也不成立,只是碰巧安全。

P2-1 平台新建的 PostHog project 默认关着 session replay

#81 的 EnsureProject 只传 name,PostHog 新 project 的 session_recording_opt_in 默认 false。这里精心配的 session_recording / capture_performance / 10% 采样在项目打开录屏前不会产生任何东西,ANALYTICS.md 的回放段落描述的是不会发生的行为。已在 #81 提了 provisioning 时 PATCH;这边二选一:等平台侧落地,或先把 ANALYTICS.md 改成"回放默认关"。

P2-2 URL 脱敏漏掉 $set_once 里的人物属性

sanitizePostHogProperties 只处理四个顶层 URL 键。已在锁定的 posthog-js 1.425.1 dist/array.full.js 里核实:它会通过 register_once 写入 $initial_person_info: { r: referrer, u: 完整 URL }(老字段 $initial_current_url 同理),走 $set_once 随早期事件和 identify 上报。首次访问带 ?token=…、OAuth 回调 ?code=…、magic link 这类 URL 会原样进 PostHog 的 person profile。posthog-js 自带 mask_personal_data_properties 开关(正是控制这块的),14 个 helper 都没开;加 mask_personal_data_properties: true(需要的话配 custom_personal_data_properties)最省事,否则 sanitizer 要递归进 $set / $set_once

P2-3 描述说"语义转化事件",实际只有一个模板在发

lib/analytics.ts 之外调用 analytics.identify/track/…Completed 的只有 ai-pdf-chatbot 的登录/注册表单两处;其余 13 个模板只 initializeAnalytics(),上报的就是 pageview / pageleave / performance。data-private 全仓 0 处,录屏只遮 input。要么把六个基线事件接到各模板的成功路径,要么把 PR 描述和 ANALYTICS.md 收敛成"目前 pageview + performance,语义事件已提供接口"。(cubic 在 InsForge#60 也点了这两条。)

P3

  • 采样在每次整页加载时 Math.random() 重掷,同一用户的会话会被录成碎片。平台拥有 project,采样率放服务端(project 设置)更合理,也顺手解决 P2-1。
  • ai-pdf-chatbotinstrumentation-client.ts 改成直连 us.i.posthog.com 后,next.config.js 里的 /ingest/* rewrites 成了死配置;要么保留反代(api_host: '/ingest' + ui_host,抗广告拦截),要么删掉。另外它的 posthog-js^1.383.3,其它都是 ^1.425.1
  • insight-flow-agent-chat/index.html 的配置脚本少了 vite-ignore,其它四个 Vite 模板都有。实测不带也能过构建且脚本原样保留,纯一致性问题。
  • PII_KEY(^|_)name(_|$) 也会命中 $os_name 这类 PostHog 自带键,今天无害,但这个正则同时在当 PII 过滤器和 PostHog 属性过滤器用。

@xiongxz

xiongxz commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

已按首轮 review 在 8d00d19 集中处理:

  • 9 个 Next 模板在首次初始化之外增加 DOMContentLoaded 重试,覆盖 async chunk 先于 runtime config 执行的情况。
  • 14 个 helper 全部开启 personal-data masking;系统 $ 属性不再被 name 正则误删。
  • replay 改用 PostHog session 级 sampleRate,不再每页 Math.random 重掷;平台 #81 同时负责开启 Project 的 session replay。
  • PR 描述和 ANALYTICS.md 已收敛为:自动 pageview/pageleave/performance,语义转化事件是需在业务成功点接入的 helper,不再过度声明。
  • 移除 ai-pdf-chatbot 已失效的 /ingest rewrites,统一 posthog-js 版本;补齐 Vite ignore 一致性和契约检查。
  • data-private 保留为客户内容区域的显式标记,因为无法对所有模板做无差别整页遮挡;默认仍 mask 全部 input 和个人属性。

14/14 analytics contract、17/17 scripts tests、代表性 Next/Vite production builds 和 diff 检查均已通过。

@xiongxz

xiongxz commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Review(二轮,8d00d19)

首轮条目全部闭环,LGTM 可合。本地验证:node scripts/check-analytics.mjs 14/14;scripts vitest 17/17;ai-pdf-chatbot 的 package-lock.json 已同步到 1.425.1(npm ci 不会炸)。

首轮条目复核

  • P1 Next 顺序竞争:9 个 instrumentation-client.ts 在首次初始化后按 document.readyState === 'loading'DOMContentLoaded 再调一次;initializeAnalytics 自身有 posthog.__loaded 守卫,二次调用安全。同步的 <head> 脚本在 DOMContentLoaded 前一定已执行,逻辑成立;check-analytics.mjs 也钉了这条。
  • P2-1 replay 默认关:平台 #81 已在创建/复用 project 时打开 session_recording_opt_in
  • P2-2 $set_once URL:14 个 helper 都加了 mask_personal_data_properties: true
  • P2-3 描述过度声明:PR 描述与 ANALYTICS.md 都改成"自动 pageview/pageleave/performance,语义事件是需要接入的 helper",和代码一致了。
  • 采样重掷:改成 session_recording.sampleRate。我专门核了 1.425.1 的 dist/array.full.js:它确实读客户端配置 session_recording.sampleRate(与远端配置取其一,带同名归一化),所以这是真正的按 session 稳定采样,不是被忽略的死配置。
  • ai-pdf /ingest 死配置删除、版本统一、$ 前缀键不再被 PII 正则误删,都对。

剩两条建议级

  • insight-flow-agent-chat/index.html 里补的是 <!-- vite-ignore --> HTML 注释。Vite 只认标签上的 vite-ignore 属性(以及 import() 内的 /* @vite-ignore */),HTML 注释不是指令。首轮实测过不带也能过构建、脚本原样保留,所以无害;但既然是为了和另外四个模板一致,就用属性形式,别留一个看起来生效其实没生效的注释。
  • data-private 仍是 0 处。作者的立场(显式标记而非整页遮挡)我接受;只是现在 replay 已在平台侧真正打开了,建议至少给 chatbot / insight-flow-agent-chat / ai-pdf-chatbot 的对话区容器加上,这三个模板的正文天然就是用户内容。

@xiongxz

xiongxz commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

二轮两条建议均已处理,提交 7c98fe9

  • insight-flow-agent-chat 改为真正生效的 <script vite-ignore ...> 属性写法;analytics contract 新增断言,防止再退回无效 HTML 注释。
  • chatbotinsight-flow-agent-chatai-pdf-chatbot 新建/历史对话内容容器全部加 data-private,避免用户对话正文进入 session replay;契约脚本锁定这四个敏感页面。

14/14 analytics contract、17/17 scripts tests 通过;三个受影响模板的 production build 均通过,diff 检查干净。

@xiongxz

xiongxz commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Review(三轮,7c98fe9)—— LGTM,可合

  • insight-flow-agent-chat/index.html 改成 <script vite-ignore …> 属性,五个 Vite 模板一致;契约脚本加了 \bvite-ignore\b 断言,再退回注释形式会红。
  • 三个对话类模板的对话容器加了 data-private(chatbot 滚动区、insight-flow chat-transcript、ai-pdf 两个 chat 页),契约脚本锁定这四个文件。配合 helper 里的 blockSelector: '[data-private]',回放里整个对话区会被占位块替代而不只是遮文字,这是最保守的做法,符合 ANALYTICS.md 的口径。
  • node scripts/check-analytics.mjs 14/14,scripts vitest 17/17。

三个 PR 一起看的话,跨仓契约到这一轮没有再变:运行时配置字段、JWT 参数、查询白名单、密钥派生语义都对上了。

@xiongxz
xiongxz merged commit fc0759b into main Sep 4, 2026
4 checks passed
Sign up for free to 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