Skip to content

fix(mobile): prevent listperf sweeps from fighting tail following - #7086

Merged
MagicLizi merged 2 commits into
makecindy:mainfrom
elfcv:fix/mobile-listperf-scroll-ownership
Oct 25, 2026
Merged

MagicLizi merged 2 commits into
makecindy:mainfrom
elfcv:fix/mobile-listperf-scroll-ownership

Conversation

@elfcv

@elfcv elfcv commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

这次改了什么

摘要

修复 DEV-only listperf 自动滚动测试与“跟随最新消息”争夺位置的问题。旧测试从顶部扫描,却保持贴底意图;实际轨迹在请求 135 px 时被纠正回约 20,304 px,反复挂载列表两端,把测试自身的干扰算成业务卡顿。

自动测试现在通过已有 focusedItemKey 历史定位入口进入首条消息,再开始扫描;每个 runKey 使用独立列表生命周期。默认 recycle=0 与当前正式列表一致,显式 recycle=1 仍可做 A/B。离开页面或切换参数时,现在会取消等待计时器与滚动帧循环;扫描绑定本轮列表 API,旧扫描不能写入新列表。

变更类型

  • fix 缺陷修复

范围

  • 关联:perf(clients): 减少桌面与手机重复渲染、缓存写入及轮询 #7053 合并后的模拟器性能报告及其后续排查。
  • 修改 DEV listperf、其 scrollProfiler、对应原生生命周期 guard,并新增真实测试页面的生命周期回归。不修改正式消息列表、自动跟随控制器、缓存优化、原生配置或依赖。
  • 用户可见变化:只影响 DEV 性能测试页的初始位置、独立运行和默认测试选项。
  • breaking change:无。

UI 变化

  • 引用的设计规范:不涉及:沿用现有组件、样式和消息定位行为,仅修复 DEV 测试驱动;无正式产品界面变化。

怎么验证的

自动验证

  • pnpm --filter mobile typecheck 通过。
  • 定向 Vitest:listPerfHarness、messageNativeLifecycleContracts、messageScroll、messageTailFollower,95 项通过,2.22 秒。
  • 完整 Mobile guard:41 个文件通过,7.1 秒。
  • 新增 listPerfHarness.test.tsx:先红后绿(修复前 3 失败 / 1 通过,修复后 4 通过);独立运行 1.67 秒。真实组件 + 真实 runScrollSweep,仅替换原生/路由端口,使用虚拟时钟。覆盖 settle 期间切换、运行中切换、卸载,以及连续运行与回收 opt-in;旧控制器测试未挂载测试页,因此漏掉这类生命周期问题。tier 为 unit。
  • pnpm --filter mobile test:scope 通过。
  • git diff --check、pnpm check:test-hygiene(363 个文件)及 pnpm check:dco(本 PR 2 个提交)通过。
  • Mobile 未定义独立 lint 脚本/ESLint 配置;直接运行 ESLint 返回缺少配置,未将该项记为通过。

运行验证

2026-10-25,Apple M1 / 16 GiB / macOS 27.0.1,iPhone 18 Pro / iOS 27.0 Simulator,原生 Debug / Hermes。
工作树 cindy-performance-audit,分支 fix/mobile-listperf-scroll-ownership;官方 whoami 验证 Metro :8081 归属和源码指纹 e500a7f0d+05368c5fd7,已观察新 bundle 和模拟任务首页。该 dirty 指纹对应本提交的同一文件内容;原生指纹 599a8b60981045ea119d52ab4a1842fbc10effbc 匹配。

最小复现:listperf?turns=40&media=0&auto=1&vel=45&recycle=0。

  • 修复前原生轨迹:请求 135 px,实际偏移却在约 20,304 px;153 次扫描请求伴随 214 次底层 offset 写入。
  • 同一进程诊断对照:跟随开启时 6.6 JS fps;通过真实滚动 handler 表达浏览历史意图后 45.0 JS fps。该对照只用于定位原因,包含运行时诊断注入。
  • 修复后不注入浏览意图:153 次请求 / 153 次 offset 写入,超过请求位置 500 px 的异常尾跳为 0;截图直接目检了正常历史位置。
  • 重复测量时断开 CDP,AppState 为 active,每场景三次,每次 150 帧 / 45 px,未并行运行本任务编译或测试:
场景 三次 JS fps 中位数
原代码,80 条纯文本,recycle=0 9.5 / 10.6 / 9.4 9.5
修复后,80 条纯文本,默认 recycle=0 41.1 / 48.6 / 48.4 48.4
修复后,600 条,media=1,默认 recycle=0 45.0 / 43.1 / 44.5 44.5

修复前 80 条场景重复解析 621–733 次,修复后 42–69 次。这里减少的是测试引入的往返渲染,不是正式 App 获得同等倍数提速。

2026-10-25 追加验证(93fca8f75)

  • Greptile 指出的旧回收默认值 guard 已先复现再修复;全部 Mobile guard 通过,未删除或弱化正式列表的原生生命周期断言。
  • 新发现:切到另一组参数/手动场景后,旧扫描仍通过可变 API ref 滚动新列表。模拟器 2 秒观察窗记录 16 次残留调用;增加计时器/rAF 取消与单轮 API 绑定后为 0。组件回归同时覆盖开始前退出和卸载。
  • 用官方入口重启本工作树 Metro 与模拟器测试 App,源码身份为 93fca8f、原生指纹保持匹配;正式 Cindy 未重启或更新。
  • 最终提交原生轨迹:153 次扫描请求 / 153 次底层写入,异常尾跳为 0;截图观察到第 13–14 轮历史内容及 done 状态。Hermes 内 mobileRuntimeIdentity() 返回提交 93fca8f;诊断注入已清除。
  • 新提交重复测量时断开 CDP,两场景各 3 次 / 150 帧,AppState 均 active,本任务没有并行编译或测试:
新提交场景 三次 JS fps 中位数
80 条,media=0,recycle=0 43.9 / 45.3 / 48.7 45.3
600 条,media=1,recycle=0 41.7 / 43.3 / 40.5 41.7

这些数据是测试驱动的重复性验证,不是产品性能提速比例。旧 head Windows CI 的两个失败位于未改动的 Desktop guestHost resume 测试;尚未证明根因或 Windows 通过。新 head 完整 CI、正式审批与正常合并门禁仍以实时结果为准。

最新 CI 结果(93fca8f75)

2026-10-25 主任务直接复核:client-ci 40153329646 已全部成功,包含 Linux/Windows 各四组、Windows runner/database、Desktop Git integration、verify-checks、verify 与 Windows 汇总;该 head 的 DCO、PR design-basis 也成功。旧 head 的 Windows resume 失败没有在此次运行重现,不据此宣称其根因已定位。

现有审阅线程全部解决;仍缺正式 Approve,当前账号上游 READ,等待维护者按正常规则批准/合并。模拟器与本地测试证据仍对应同一 head,边界保持上述说明。

未执行的验证

  • Release/Profile、物理设备、耗电、泄漏和真实网络未验收;没有供应商推理。
  • listperf 本来就有 DEV-only 门禁。本轮先通过原生轨迹定位并消除测试干扰,没有为测试开放生产调试入口。
  • JS requestAnimationFrame 间隔不是原生 UI/GPU 帧率;150 帧只扫描一段历史,不代表扫遍全部 600 条消息。media=1 不代表每个富内容块均被完整验收。
  • 系统后台负载未完全受控(采样期间曾观察到系统 ANE 编译进程);没有声称达到稳定 60 fps 或最终性能验收通过。
  • perf(clients): 减少桌面与手机重复渲染、缓存写入及轮询 #7053 原有 42 项缓存/额度/Mermaid 功能断言不受此更正影响;旧列表 5–8 fps 结果不应再作为正式列表性能判断依据。

风险

风险分类

  • 其他:DEV 测量口径改变,旧新测试分数不能直接作为产品性能增益比较。

影响与回滚

  • 仅 DEV 性能测试路由;正式列表行为、授权、存储与原生指纹输入不变。
  • 回滚本提交可恢复原测试驱动,不涉及数据迁移。

提交前检查

  • 已 review 完整 diff
  • DCO 签名提交
  • 未提交凭证、私人数据或测试注入
  • 已列明实际验证与边界

Signed-off-by: elfcv <270533111+elfcv@users.noreply.github.com>
@elfcv
elfcv requested a review from a team as a code owner October 25, 2026 01:43
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-25T01:46:30.855346Z
We couldn’t complete this review. Comment @codex review to try again.
b49fb85 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@greptile-apps

greptile-apps Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Low impact] The changes are limited to the DEV list performance page.

合并前需要同步更新默认值对应的测试断言。

Findings

  1. P1 Mobile 测试失败 ▶
Fix with agent prompt
### Issue 1
apps/mobile/app/listperf.tsx:39
将 `recycle` 默认值改为关闭后,`messageNativeLifecycleContracts.test.ts:64` 仍要求源码包含旧表达式 `const recycle = params.recycle !== '0';`。运行完整 Mobile 测试套件时,该断言必然失败。请同步更新断言,保留对显式开启回收的检查。

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

本 PR 调整 DEV 性能测试页,避免自动扫描与贴底跟随争夺位置。

  • Automatic list tests start in history with recycling off by default.

Reviews (1) · Last reviewed commit: "fix(mobile): keep list performance sweep..." · Reviewed by Greptile

Comment thread apps/mobile/app/listperf.tsx Resolved
Signed-off-by: elfcv <270533111+elfcv@users.noreply.github.com>
@MagicLizi MagicLizi added the touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) label Oct 25, 2026
@MagicLizi

Copy link
Copy Markdown
Contributor

命中 UI 路径(apps/mobile/app/listperf.tsx / apps/mobile/src/debug/scrollProfiler.ts)但 description 未附界面效果证据——建议补充改动后效果:截图/录屏,或改动后界面的 HTML 页面(```html 代码块、.html 附件或在线预览链接),便于确认界面符合 DESIGN.md 设计规范。

(非阻断提醒:本页为 DEV-only 性能 harness,不影响正式产品界面,不因此打回。)

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review report — PR #7086(head 93fca8f)

fix(mobile): prevent listperf sweeps from fighting tail following
作者 elfcv · base 08d1788(main) · 标准档审查

Findings

没有 P0/P1。本次改动全部落在 DEV-only 性能测试链路(__DEV__ 门禁 + dev deeplink 页面、
debug 测量工具)与测试,未触碰正式消息列表、自动跟随控制器、原生配置或依赖;共享符号
runScrollSweep 唯一调用方就是本 harness,返回值从 void 改为取消函数不破坏任何其他
调用点;focusedItemKey / devExposeList / devRecycleItems 均为 MessageRenderer 既有
API,用法与既有语义(显式消息定位拥有初始位置、关闭贴底跟随)一致。

维护者确认门(product 触发)语义定性:不 hold。逐文件判据——

  • apps/mobile/app/listperf.tsx:DEV-only 临时 harness(__DEV__ 门禁,生产构建只显示
    "DEV-only" 提示),bugfix(扫描与贴底跟随争抢滚动所有权、run 生命周期泄漏),对最终
    用户零可见差异;
  • apps/mobile/src/debug/scrollProfiler.ts:DEV-only 测量工具,纯技术改动(返回取消函数);
  • apps/mobile/src/__tests__/listPerfHarness.test.tsx:新增回归测试(测试路径确定性排除);
  • apps/mobile/src/__tests__/messageNativeLifecycleContracts.test.ts:断言机械同步 1 行,
    不改变 guard 规则含义。
    按 product 门判据「用户看不看得见」:生产用户不可见、无外观/布局/文案/交互流向变化 →
    执行 fallback=review,不创建讨论 issue、不挂确认标签。

Rule coverage — 读取的规则文件及逐项结论

  • AGENTS.md:仓库边界、安全底线 —— 无凭证/敏感数据改动,无跨仓改动。通过。
  • docs/dev-rules/development-workflow.md(P0/P1/P2 口径、验证真实性):PR 模板三段齐全、
    title 类型 fix 与 diff 一致;验证声明逐条核对(见 Verification)。通过。
  • docs/dev-rules/environment-setup.md:审查 worktree 按文档安装依赖后验证。通过。
  • docs/dev-rules/mobile-development.md / docs/dev-rules/remote-and-mobile-adaptation.md
    (apps/mobile 命中):未触碰 app.json/app.config.js/eas.json/package.json/
    plugins//modules/ 等 runtime fingerprint 输入,无冷更边界问题;未触碰 device-link、
    workdir 或 IPC。通过。
  • docs/design-rules/DESIGN.md(UI 命中,强制):改动仅 DEV harness 页面内的既有
    <Text> 展示串(phase 文案)与行为,未新增/修改样式、组件或主题 token,无硬编码绕过
    token 的新增(styles 为既有未改动历史代码)。无设计硬伤。
  • rule-map「审查时的固定问题」逐项:①单一目的(测试驱动滚动所有权修复),描述与 diff/
    测试一致;②共享符号 runScrollSweep 读方(仅 listperf)已核;错误/回滚路径:effect
    清理取消 timer+rAF、cancelled 双保险挡 onDone,无悬挂句柄;③无权限/凭证/跨平台面;
    ④无新增依赖、无危险写法、无新数据流向(security 扫描 hard/soft 均 0 命中);
    ⑤新增真实回归测试 4 项且先红后绿;⑥未把流程塞进 Core;⑦UI 证据缺失属非阻断提醒
    (DEV-only 页面,PR 已注明「不涉及」理由);⑧不涉原生配置,无冷更;⑨无 P0/P1。

Verification — 实际运行的命令、结果、未执行项及原因

(均在审查 worktree(head 93fca8f)内运行,日志保留完整输出与退出码)

命令 结果
pnpm install --frozen-lockfile --prefer-offline --prod=false EXIT=0
pnpm --filter mobile run typecheck EXIT=0(与 PR 声明一致)
vitest run listPerfHarness / messageNativeLifecycleContracts / messageScroll / messageTailFollower EXIT=0,95 项全过(与 PR 声明的 95 项一致)
pnpm --filter mobile test(整包) EXIT=0,768 文件 / 10801 项全过
pnpm --filter mobile run test:scope EXIT=0,mobile-scope-guard passed
git diff --check 08d1788...HEAD EXIT=0
pnpm check:dco EXIT=0,2 个提交均签名(e500a7f0..93fca8f)
pnpm check:test-hygiene EXIT=1:报 runtimePrompt.test.ts sourceReads: 0 -> 4、MacModifierShortcutListenerStartRace.test.ts resetModules: 0 -> 1。经核:两个文件与 scripts/test-hygiene-baseline.json 均未被本 PR 触碰(git diff --name-only e500a7f..HEAD 仅 4 个 mobile 文件);失败源于分支落后 main 223 个提交,main 侧已清理这两个 desktop 测试并收紧 ratchet 基线(363→361 条),本分支旧基线与 trusted base(origin/main)对比被判"上调"。合并结果按三方合并取 main 侧文件与基线,合入后即通过;属分支落后 artifact,不计入本 PR 缺陷

未执行:模拟器/真机运行验证(PR 已提供 2026-10-25 iOS 模拟器轨迹与 fps 对照证据,本次
审查未复跑);security-scan.mjs 重档扫描(standard 档不适用,security 门 0 命中)。

其它观察(不阻断):bot 静默期按 20 分钟超时放行(copilot / chatgpt-codex-connector 未对
当前 head 表态,迟到意见由 post-merge-sweep 兜底);Codex 汇总标记自称 b49fb85 上有未解决
findings,但其 review 状态为 failed 且 GitHub 上无对应未解决 thread(greptile P1 thread 已由
93fca8f 修复并 Resolve);UI 证据缺失提醒已按非阻断规则处理。

Intent alignment

  • 描述来源:PR 当前 body(2026-10-25,含两轮修复后的验证说明);关联 #7053 合并后的模拟器
    性能报告排查(无独立需求 issue,signoff 无讨论 issue)。
  • 初始实现:b49fb85f2ecb75f476648791185f2cc9aea40050(fix(mobile): keep list performance
    sweep in history mode,基线 e500a7f0d9721b26766ddd5e9e73e71ee3934254):listperf.tsx
    8+/3- —— focusedItemKey 走真实消息定位进入历史模式、key={runKey} 独立列表生命周期、
    recycle 默认对齐正式列表。已实际读取该 revision 的 diff 核对。
  • 现在要合什么:head 93fca8f7 = 初始实现 + 93fca8f757(cancel stale list performance
    sweeps):effect 清理取消等待 timer 与扫描 rAF、runScrollSweep 返回取消函数且取消后不
    发布未完成测量、扫描闭包绑定本轮列表 API、新增真实组件回归测试 4 项、同步 guard 断言。
原目标/初始行为 最终行为与影响 代码/提交证据 变化依据或待确认问题
自动扫描不再被贴底跟随纠正回尾部(原始目标) focusedItemKey 显式进入历史浏览意图,nearBottom=false,扫描全程不受 tail-follow 纠正 b49fb85 apps/mobile/app/listperf.tsx(MessageRenderer 既有 focusedItemKey 路径,MessageRenderer.tsx:2746-2783) 一致:围绕同一目标的实现
每个 run 独立、旧扫描不得写入新列表 key={runKey} 重挂 + 清理取消 timer/rAF + scrollTo 闭包绑定本轮 api 93fca8f listperf.tsx、scrollProfiler.ts 一致:PR body 明示的缺陷修复
recycle 默认与正式列表一致 params.recycle === '1'(默认关),显式 recycle=1 仍可 A/B b49fb85;guard 断言在 93fca8f 同步 一致:修复 greptile P1(断言与默认值不同步),thread 已 Resolve

结论:一致。两轮 Codex/Greptile 意见及修复(greptile P1:recycle 默认值改动使
messageNativeLifecycleContracts.test.ts:64 断言失效 → 93fca8f 同步断言并补回归)均围绕
原始目标,未删除/削弱/反转原承诺,无无关扩范围,未用禁用功能或绕开验证消除报错。核心
目标(测试驱动不与贴底跟随争抢滚动、run 间生命周期隔离)在最终 diff 中可核实落实。

Overall

pass —— 没有 P0/P1。原意一致性复核完成,无未裁定的核心方向问题。

@MagicLizi
MagicLizi merged commit b357665 into makecindy:main Oct 25, 2026
17 checks passed
@MagicLizi

Copy link
Copy Markdown
Contributor

合了。listperf 的自动扫描终于不用跟「跟随最新消息」抢滚动所有权,模拟器上的性能数据以后能直接采信;顺手补上的生命周期回归测试把这类挂载期竞态也钉住了,谢谢。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants