Skip to content

fix(stats): 统计面板只在统计页挂载 - #3

Merged
devcxl merged 1 commit into
mainfrom
fix/stats-mount-only-on-analytics-pages
Oct 19, 2026
Merged

devcxl merged 1 commit into
mainfrom
fix/stats-mount-only-on-analytics-pages

Conversation

@devcxl

@devcxl devcxl commented Oct 18, 2026

Copy link
Copy Markdown
Owner

问题

修复 #2:普通聊天界面也会注入「ChatGPT 使用统计」面板,内容停在「等待官方统计数据」。

根因

  1. stats 内容脚本必须匹配 https://chatgpt.com/*:ChatGPT 是 SPA,站内路由切换不会重新注入内容脚本,只匹配统计页 URL 会让「首页 → 统计页」的导航失效。
  2. 但挂载与否只交给了 WXT 的 ui.autoMount(),其判定条件仅为 ANALYTICS_ANCHOR_SELECTOR 能匹配到元素。选择器的回退项是通用布局类名(div:not([hidden]) > div.flex.flex-col > div.flex.w-full.flex-col、div.pb-8:nth-child(2)),聊天页主列同样满足,面板就被追加到聊天布局末尾。
  3. 面板没有路由判断,且默认展开,因此挂载后立即显示占位内容。

修改

entrypoints/stats.content/index.ts:

  • 新增 isAnalyticsPage():按 location.pathname 判定 /settings/usage 与旧版 /codex/cloud/settings/analytics(兼容末尾斜杠)。
  • 监听 wxt:locationchange:SPA 前进/后退时重新同步,离开统计页立即 ui.remove()。
  • 用显式的 mount/unmount sync() 替代 autoMount():非统计页不挂载;统计页等待官方卡片容器异步渲染后再挂载;官方容器被整体替换时重新挂载。
  • matches 保持不变(SPA 需要),补充注释说明页面过滤由运行时路由判定负责。

验证

  • npm run lint、npm run type-check:通过
  • npm run build:chrome、npm run build:firefox、./scripts/verify-manifest.sh:通过
  • 在 Chromium(Playwright,加载构建产物)中用 https://chatgpt.com/** fixture 做行为验证,全部通过:
    • 首页 / 聊天会话 / /settings:不挂载
    • /settings/usage(含末尾斜杠)/ 旧版 Codex 统计页:挂载,且位于统计卡片容器末尾
    • SPA 统计页 → 聊天页:卸载;聊天页 → 统计页:挂载
    • 统计页内重复路由、官方容器整体替换:始终只有一个面板并重新挂载
    • 重新挂载后仍能消费页面统计响应并渲染数据

风险与回滚

  • 仅改变挂载时机,未改动数据解析、bridge 或图表逻辑。
  • 若官方统计页路径再次变更,需要同步更新 ANALYTICS_PAGE_PATHS;漏改的表现是统计页不显示面板(而不是错误页面显示面板)。
  • 回滚:revert 本提交即可恢复原行为。

The stats content script runs on every chatgpt.com page because SPA route
changes do not re-inject content scripts. Mounting relied on WXT's
autoMount, which only requires the anchor selector to resolve, and the
fallback selectors match generic layout containers that also exist in the
chat UI. The panel therefore appeared at the bottom of normal chat pages
while waiting for analytics data that never arrives.

Gate mounting on the analytics route and replace autoMount with an explicit
mount/unmount sync. wxt:locationchange fires before the URL commits, so
unmounting uses the event's destination path; entering the analytics route
mounts on the first render mutation after the commit. Remount when the
official container is replaced, and keep the panel when only its children
are re-ordered. The two exact match patterns are redundant with
chatgpt.com/*, which the SPA requires, so the runtime route check is the
single source of truth.

Fixes #2
@devcxl
devcxl force-pushed the fix/stats-mount-only-on-analytics-pages branch from fbd72eb to fb48d82 Compare October 18, 2026 15:22
@devcxl

devcxl commented Oct 18, 2026

Copy link
Copy Markdown
Owner Author

审查范围

Standards

仓库没有文档化的编码规范(无 CODING_STANDARDS.md / CONTRIBUTING.md / AGENTS.md),因此没有硬性违规;lint 已通过,工具能发现的问题不在报告中。以下均为 smell baseline 的判断项。

  1. Shotgun Surgery(判断项):matches 与 ANALYTICS_PAGE_PATHS 是「统计页」的两个事实源,且语义不一致(matches 允许子路径,运行时判定不允许)。
    → 已修复:删除两个冗余的精确 matches(被 https://chatgpt.com/* 完全覆盖),路由表成为唯一来源。
  2. Speculative Generality(判断项):两个精确 matches 条目是多余的。
    → 已修复(同上)。
  3. Duplicated state(判断项):let mounted 与 WXT 已提供的 ui.mounted 重复,存在漂移风险。
    → 已修复:直接以 ui.mounted 为准,删除布尔变量。
  4. Mysterious Name(判断项):sync 未说明同步什么,且与 onRemove: (mounted) 的参数名冲突。
    → 已修复:重命名为 syncPanel,删除 mounted 变量后冲突消失。
  5. Duplicated Code(判断项):每次调用中 isAnalyticsPage() 被求值两次。
    → 已修复:提升为局部变量 onAnalyticsPage。
  6. 长期全文档 MutationObserver(判断项):每次 DOM 变更都会调用一次同步逻辑。
    → 不修改:与改动前的 autoMount(waitElement 同样观察 document 的 childList/subtree/attributes)观察范围一致,非统计页只做一次 pathname 判断后返回,没有引入新的量级;按路由启停观察器会增加生命周期复杂度,收益不明确。

Minor:ui.mount() 与锚点检查同步执行,不存在「检查后锚点消失」的窗口;即使抛错也会在控制台可见,并由下一次 DOM 变更重试。

Spec

(a) 缺失或部分实现

  1. 「进入统计页等待官方容器渲染后挂载」— 部分实现:仍复用合并选择器,通用回退项在统计页中间渲染阶段也可能先匹配到布局容器,无法区分「官方卡片容器」与「通用布局容器」。
    → 保留:在当前统计页上首次匹配到的容器就是可用位置(功能在此页面正常),要严格区分需要真实页面 DOM;贸然收紧会破坏已知可用路径。
  2. 「离开统计页立即卸载」— 原实现未达成。
    → 已修复:WXT 的 wxt:locationchange 在 URL 提交前触发,原代码读取 location.pathname 得到的是旧地址;改为使用 event.newUrl.pathname。已用「仅 pushState、不改 DOM」的场景验证:修复前面板残留,修复后立即卸载。
  3. 期望 URL 含 ?tab=analytics,但 isAnalyticsPage() 只判断 pathname。
    → 接受:Issue 的修复方向明确写的是「按 location.pathname 判定」;tab 的默认值与语义未验证,按 tab 收紧可能在页面默认进入统计 tab 时误藏面板。

(b) 未被要求的改动

  • 全文档 MutationObserver:见 Standards 6,观察成本与改动前一致。
  • 容器断连即整树重建(Vue app 重建、重新订阅):原实现中容器观察器本可低成本重插,但页面观察器先执行导致该路径失效。
    → 已修复:重挂载条件改为「官方卡片容器本身断连」,仅子节点重排时由容器观察器重新插入,不做整树重建。

(c) 看似实现但实现有误

  1. wxt:locationchange 的时序问题 — 已确认为真实缺陷并修复(见 a2)。实测 Chromium:navigate 事件触发时 location.pathname 仍是旧路径,微任务后才提交;WXT 在事件中同步派发 locationchange。
  2. 「兼容末尾斜杠」— 验证正确:/settings/usage/、/codex/cloud/settings/analytics/ 均能归一化匹配,/ 归一化为空不会误匹配。

总结

  • Standards:6 项,均为判断项,无硬性违规;最严重的是挂载状态存在第二事实源(fix(stats): 统计面板只在统计页挂载 #3)与路由来源重复(Example #1),均已修复。
  • Spec:5 项;最严重的是 locationchange 在 URL 提交前触发导致「离开统计页立即卸载」失效(c1),已用实测确认并修复。

审查后修复已合入 fb48d82,CI(39140216385)通过;Playwright 行为验证(普通页不挂载、统计页挂载、SPA 双向切换、仅路由变化、容器替换、重新挂载后渲染数据)全部通过。

@devcxl
devcxl merged commit 30beb1e into main Oct 19, 2026
1 check 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