Skip to content

Fix long session timeline scrolling - #6526

Merged
callmeYe merged 12 commits into
mainfrom
codex/fix-session-timeline-scroll
Jul 9, 2026
Merged

Fix long session timeline scrolling#6526
callmeYe merged 12 commits into
mainfrom
codex/fix-session-timeline-scroll

Conversation

@callmeYe

@callmeYe callmeYe commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This fixes the Web Shell session timeline for conversations with many turns. The timeline stays centered in the viewport, the marker list scrolls internally while keeping native scrollbars hidden, the current marker is kept visible without scrolling the chat transcript, and timeline details render as a floating layer outside the scroll/fade area so hover and focus details are not clipped.

Why it's needed

Long sessions could make the timeline difficult to use: the rail could feel visually off-center, the earliest turns could be hard to reach and select, and the details tooltip near the top of the visible marker range could be clipped. The fix keeps the timeline usable for dense sessions while preserving the existing marker hover, current-range highlighting, and click-to-turn behavior.

Reviewer Test Plan

How to verify

Open a long Web Shell session with 60+ turns and confirm the left timeline remains vertically centered, scrolls internally with no visible native scrollbar, keeps the first and last markers selectable, fades near the top and bottom, and shows hover/focus details without clipping. Also verify that short sessions still use the existing visibility behavior and narrow/mobile layouts still hide the timeline.

Evidence (Before & After)

Before: long sessions could make the visual timeline range hard to navigate, with top-range tooltip details at risk of clipping and the earliest markers hard to select. After: tested in Chrome on macOS with the long session 6cb9a056-9dcd-48e6-9d38-2574f4c50aa3; the timeline viewport computed scrollbar-width: none, its ::-webkit-scrollbar was display: none with zero width/height, and the visual crop showed only timeline markers with no native scrollbar. Automated regression coverage: npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx passed 38 tests. Build verification: npm run build --workspace=packages/web-shell passed.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

Local dev verification used npm run dev:daemon -- --workspace /Users/ethan/Projects/qwen-code with Chrome against the local Web Shell.

Risk & Scope

  • Main risk or tradeoff: scrollbar styling is browser-specific, so the fix keeps both WebKit pseudo-element hiding and Firefox-style scrollbar-width rules scoped to the timeline viewport.
  • Not validated / out of scope: Windows and Linux visual browser verification were not run locally.
  • Breaking changes / migration notes: None.

Linked Issues

N/A

中文说明

What this PR does

这个 PR 修复了 Web Shell 在长会话下的左侧会话时间轴。时间轴会保持在视口内垂直居中,marker 列表自身滚动但保持原生滚动条隐藏,当前 marker 会在时间轴内部保持可见且不会触发聊天正文滚动,详情浮层会脱离滚动和渐变区域渲染,避免 hover/focus 时被裁剪。

Why it's needed

长会话下时间轴会变得难用:轨道视觉上可能不够居中,最早轮次可能难以到达和选中,可视 marker 顶部附近的详情 tooltip 也有被裁剪的风险。这个修复让密集会话里的时间轴保持可用,同时保留原有 marker hover、当前范围高亮和点击跳转行为。

Reviewer Test Plan

How to verify

打开一个 60+ 轮的 Web Shell 长会话,确认左侧时间轴保持垂直居中、内部可滚动但不显示原生滚动条、首尾 marker 都能选中、顶部和底部有淡出效果,并且 hover/focus 详情不会被裁剪。同时确认短会话仍沿用原有显示门槛,窄屏/移动布局仍隐藏时间轴。

Evidence (Before & After)

修复前:长会话下可视时间轴范围难以导航,顶部附近的详情 tooltip 存在被裁剪风险,最早 marker 也可能难以选中。修复后:已在 macOS Chrome 中用长会话 6cb9a056-9dcd-48e6-9d38-2574f4c50aa3 验证,timeline viewport 的 computed style 为 scrollbar-width: none::-webkit-scrollbardisplay: none 且宽高为 0,视觉裁剪图里只剩时间轴 marker,没有原生滚动条。自动化回归:npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx 通过 38 个测试。构建验证:npm run build --workspace=packages/web-shell 通过。

Tested on

OS Status
🍏 macOS ✅ 已测试
🪟 Windows ⚠️ 未测试
🐧 Linux ⚠️ 未测试

Environment (optional)

本地验证使用 npm run dev:daemon -- --workspace /Users/ethan/Projects/qwen-code 启动 Web Shell,并在 Chrome 中测试。

Risk & Scope

  • Main risk or tradeoff: 滚动条样式存在浏览器差异,因此同时保留 WebKit 伪元素隐藏和 Firefox 风格 scrollbar-width,并且只作用在时间轴 viewport 上。
  • Not validated / out of scope: 本地未做 Windows 和 Linux 的视觉浏览器验证。
  • Breaking changes / migration notes: 无。

Linked Issues

N/A

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: The PR describes a real, observable UI issue — long session timelines expose a native gray scrollbar, clip tooltip details, and make earliest turns hard to select. The author tested with a specific long session (6cb9a056-...) and provides Chrome DevTools evidence of computed scrollbar-width: none after the fix. No linked issue, but the problem is concrete and reproducible in the web shell.

Direction: Aligned. This is a focused UI usability fix for the web-shell timeline — keeping it usable for dense sessions. No direction concerns.

Size: Not applicable (no core paths touched; all changes are in packages/web-shell/).

Approach: The scope feels right — 3 files, all focused on the timeline component. CSS changes for scrollbar hiding and centering, TSX changes for scroll management and floating detail layer. No unrelated changes visible. The 71 new test lines provide regression coverage. Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:PR 描述了一个真实可观测的 UI 问题——长时间轴暴露灰色原生滚动条、裁剪详情 tooltip、最早轮次难以选中。作者用具体长会话(6cb9a056-...)测试,并提供了修复后 computed style 的 DevTools 证据。没有关联 issue,但问题是具体且可复现的。

方向:对齐。这是一个针对 web-shell 时间轴的 UI 可用性修复,保持密集会话下的可用性。无方向顾虑。

规模:不适用(未触及核心路径,所有改动在 packages/web-shell/)。

方案:范围合理——3 个文件,都聚焦在时间轴组件。CSS 隐藏滚动条并居中,TSX 管理滚动和浮动详情层。未见无关改动。71 行新测试提供回归覆盖。进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

The changes are clean and well-focused. Three things stand out as good decisions:

  1. Viewport wrapper — wrapping the <ol> in a scrollable <div> with overflow-y: auto + hidden scrollbar is the right approach. The double-class specificity trick (.sessionTimelineViewport.sessionTimelineViewport::-webkit-scrollbar) correctly beats the app-wide scrollbar rule without resorting to !important inflation.

  2. Floating tooltip — moving from per-entry CSS-only :hover/:focus-visible details to a single React state-managed tooltip rendered outside the scroll viewport solves the clipping problem cleanly. The useLayoutEffect for viewport-edge clamping (12px margin) keeps the tooltip on-screen.

  3. Viewport-relative sticky — changing top: 50% to top: 50vh correctly makes the timeline stick relative to the viewport rather than the nearest scrolling ancestor, which matters once the timeline is inside a scroll container.

No critical blockers. No security issues. The revealTooltip closure is created per-render (not useCallback-wrapped) but since it's just a thin wrapper over the memoized showTooltip, the overhead is negligible for list items.

One observation (not a blocker): the tooltip clamping logic adjusts top to stay within viewport margins, but if the tooltip height exceeds the available viewport space, it could be pushed below. For typical tooltip sizes (2-3 lines) this won't happen in practice.

Testing

Unit tests — 38/38 pass

 RUN  v3.2.4 /packages/web-shell/client

 ✓ components/MessageList.dom.test.tsx (38 tests) 1135ms

 Test Files  1 passed (1)
      Tests  38 passed (38)
   Duration  2.27s

Build — web-shell builds successfully

✓ 203 modules transformed.
✓ built in 15.71s
✓ 199 modules transformed. (lib build)
✓ built in 2.76s

Real-scenario (tmux) — Not applicable. This is a browser-rendered React component (web shell UI). Cannot be tested via CLI tmux. The author tested manually with Chrome on macOS using a real 60+ turn session (6cb9a056-...), verifying scrollbar-width: none and ::-webkit-scrollbar { display: none } via DevTools. Windows and Linux browser verification were not done by the author.

中文说明

代码审查

改动干净且聚焦。三个好的设计决策:

  1. Viewport 包装器 — 用可滚动的 <div> 包裹 <ol>,配合 overflow-y: auto 和隐藏滚动条,是正确的方法。双类名优先级技巧正确覆盖了全局滚动条规则。

  2. 浮动 tooltip — 从每个 entry 的 CSS-only 详情改为单个 React 状态管理的 tooltip,渲染在滚动 viewport 之外,干净地解决了裁剪问题。

  3. Viewport 相对 sticky — 将 top: 50% 改为 top: 50vh 使时间轴相对于 viewport 固定。

无关键阻塞问题。无安全问题。

测试

单元测试 — 38/38 通过
构建 — web-shell 构建成功
真实场景(tmux) — 不适用(浏览器 React 组件)。作者在 macOS Chrome 上用真实长会话手动测试。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

This is a solid, focused UI fix. The author identified real usability issues with the long session timeline (native scrollbar leaking through, tooltip clipping, hard-to-reach first entries) and solved them with clean, well-scoped changes.

The viewport wrapper with hidden scrollbar + mask-image fade is the right approach. Moving the tooltip to a single floating element outside the scroll container correctly solves the clipping problem. The useLayoutEffect-based scroll-to-current and tooltip clamping are straightforward React patterns.

Tests pass (38/38), build succeeds, and the diff carries no unrelated changes. The only gap is Windows/Linux browser verification, which the author flagged honestly.

Approving. ✅

中文说明

这是一个扎实、聚焦的 UI 修复。作者识别了长时间轴的真实可用性问题(原生滚动条泄漏、tooltip 裁剪、首个 entry 难以选中),用干净且范围合理的改动解决了它们。

Viewport 包装器配合隐藏滚动条 + mask-image 渐变是正确方法。将 tooltip 移到滚动容器外的单个浮动元素正确解决了裁剪问题。useLayoutEffect 的滚动到当前项和 tooltip 钳位是标准的 React 模式。

测试通过(38/38),构建成功,diff 无无关改动。唯一的缺口是 Windows/Linux 浏览器验证,作者已诚实标注。

批准。✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 80.24% 80.24% 85.97% 81.24%
Core 85.8% 85.8% 87.18% 85.28%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   80.24 |    81.24 |   85.97 |   80.24 |                   
 src               |   80.45 |    78.16 |   84.21 |   80.45 |                   
  cli.ts           |   94.29 |    82.35 |     100 |   94.29 | ...66-467,477-478 
  gemini.tsx       |   71.61 |    75.95 |      80 |   71.61 | ...1042-1046,1167 
  ...ractiveCli.ts |   77.38 |     74.9 |   74.07 |   77.38 | ...2039-2041,2076 
  ...liCommands.ts |   88.34 |    83.87 |      90 |   88.34 | ...63,480,514,635 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |   61.03 |    66.01 |   85.07 |   61.03 |                   
  acpAgent.ts      |   60.74 |    65.76 |   84.97 |   60.74 | ...8492-8494,8510 
  authMethods.ts   |      92 |       60 |     100 |      92 | 33-34             
  errorCodes.ts    |       0 |        0 |       0 |       0 | 1-22              
  ...ion-skills.ts |     100 |    88.23 |     100 |     100 | 17,32             
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
 ...ration/service |   97.04 |    95.71 |   93.33 |   97.04 |                   
  filesystem.ts    |   97.04 |    95.71 |   93.33 |   97.04 | ...21-122,238-239 
 ...ration/session |   86.81 |    79.89 |    90.5 |   86.81 |                   
  ...ryReplayer.ts |   76.19 |    84.84 |   84.61 |   76.19 | ...83-398,411-412 
  Session.ts       |   87.41 |    79.24 |   91.86 |   87.41 | ...5818,5845-5849 
  ...entTracker.ts |   91.39 |    89.47 |   88.88 |   91.39 | ...31,195,266-275 
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   84.21 |    83.33 |     100 |   84.21 | ...37-153,209-211 
  tasksSnapshot.ts |   94.21 |     87.5 |     100 |   94.21 | 65-71             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ssion/emitters |   96.69 |    94.56 |   96.96 |   96.69 |                   
  BaseEmitter.ts   |     100 |    84.61 |     100 |     100 | 23,27             
  ...ageEmitter.ts |   95.37 |    95.74 |     100 |   95.37 | 48-55             
  PlanEmitter.ts   |     100 |      100 |     100 |     100 |                   
  ...allEmitter.ts |   98.46 |    94.84 |     100 |   98.46 | 321-322,423,431   
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
 ...ession/rewrite |    91.3 |    88.09 |   94.44 |    91.3 |                   
  LlmRewriter.ts   |      81 |       84 |     100 |      81 | ...,88-89,155-159 
  ...Middleware.ts |   96.74 |    86.84 |     100 |   96.74 | 135,143-145       
  TurnBuffer.ts    |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/commands      |    87.1 |    70.33 |   64.28 |    87.1 |                   
  auth.ts          |     100 |    83.33 |     100 |     100 | 11,14             
  channel.ts       |   56.25 |      100 |       0 |   56.25 | 16-20,28-36       
  extensions.tsx   |   96.77 |      100 |      50 |   96.77 | 39                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   95.45 |      100 |      50 |   95.45 | 31                
  review.ts        |   96.29 |      100 |      50 |   96.29 | 38                
  serve.ts         |   86.42 |    67.64 |     100 |   86.42 | ...00-603,617-621 
  sessions.ts      |     100 |      100 |      50 |     100 |                   
 ...mmands/channel |   80.38 |    85.68 |   82.69 |   80.38 |                   
  ...l-registry.ts |    6.52 |      100 |       0 |    6.52 | 6-33,36-54        
  ...entry-path.ts |      75 |       50 |     100 |      75 | 8-9               
  config-utils.ts  |     100 |    98.71 |     100 |     100 | 51                
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  daemon-worker.ts |   95.45 |    86.72 |   96.29 |   95.45 | ...17-518,587-588 
  ...classifier.ts |   97.59 |    92.85 |     100 |   97.59 | 47-48             
  pairing.ts       |   26.31 |      100 |       0 |   26.31 | ...30,40-50,52-65 
  pidfile.ts       |   96.77 |    88.46 |     100 |   96.77 | ...77-178,242-243 
  proxy.ts         |     100 |      100 |     100 |     100 |                   
  runtime.ts       |   77.39 |    84.44 |     100 |   77.39 | ...45-149,179-181 
  start.ts         |   75.21 |    75.28 |   66.66 |   75.21 | ...38,544-547,559 
  status.ts        |   75.38 |       40 |     100 |   75.38 | 35-37,47-48,61-72 
  stop.ts          |   39.02 |    33.33 |     100 |   39.02 | 17-19,28-54       
 ...nds/extensions |   87.25 |     89.3 |   85.24 |   87.25 |                   
  consent.ts       |   72.53 |       90 |   42.85 |   72.53 | ...86-142,157-163 
  disable.ts       |     100 |      100 |     100 |     100 |                   
  enable.ts        |     100 |      100 |     100 |     100 |                   
  install.ts       |   85.05 |    83.33 |      75 |   85.05 | ...05-208,211-220 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |     87.5 |     100 |     100 | 18                
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  sources.ts       |   93.42 |    87.09 |   92.85 |   93.42 | ...4-66,96-98,167 
  uninstall.ts     |    37.5 |      100 |   33.33 |    37.5 | 23-45,57-64,67-70 
  update.ts        |   96.29 |      100 |     100 |   96.29 | 101-105           
  utils.ts         |      75 |    53.84 |     100 |      75 | ...27-131,133-137 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 ...amples/starter |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-64              
 src/commands/mcp  |   90.15 |    84.39 |   83.33 |   90.15 |                   
  add.ts           |    99.3 |    96.07 |     100 |    99.3 | 154-155           
  approve.ts       |   76.19 |     87.5 |   66.66 |   76.19 | ...,89-99,114-124 
  list.ts          |   92.59 |    83.87 |      80 |   92.59 | ...62-164,180-181 
  reconnect.ts     |   78.73 |    66.66 |   85.71 |   78.73 | 42-55,168-190     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   34.92 |    62.96 |      25 |   34.92 |                   
  cleanup.ts       |   17.94 |      100 |       0 |   17.94 | ...01-106,108-109 
  fetch-pr.ts      |   11.36 |      100 |       0 |   11.36 | ...80-201,203-204 
  load-rules.ts    |   11.32 |      100 |       0 |   11.32 | ...41-153,155-156 
  ...uggestions.ts |   79.41 |    86.36 |      50 |   79.41 | ...76-198,200-201 
  pr-context.ts    |   10.67 |      100 |    12.5 |   10.67 | ...71-386,388-389 
  presubmit.ts     |   67.75 |       32 |   83.33 |   67.75 | ...37-238,277-302 
 ...nds/review/lib |      30 |      100 |       0 |      30 |                   
  gh.ts            |   22.58 |      100 |       0 |   22.58 | ...49,53-54,62-69 
  git.ts           |   22.72 |      100 |       0 |   22.72 | 15-18,29-39,43-44 
  paths.ts         |   52.94 |      100 |       0 |   52.94 | ...26,37-38,42-43 
 ...mands/sessions |   91.56 |    86.95 |   83.33 |   91.56 |                   
  common.ts        |     100 |      100 |     100 |     100 |                   
  list.ts          |   90.96 |    86.66 |   81.81 |   90.96 | 208-219,221-222   
 src/config        |   94.04 |    87.42 |   95.23 |   94.04 |                   
  auth.ts          |   89.35 |    83.56 |     100 |   89.35 | ...97-298,314-315 
  ...eMcpImport.ts |   87.91 |    81.52 |     100 |   87.91 | ...63-371,453-454 
  config.ts        |   86.98 |    85.71 |   84.37 |   86.98 | ...2291,2293-2301 
  ...heme-names.ts |     100 |      100 |     100 |     100 |                   
  environment.ts   |   94.18 |    89.47 |   94.73 |   94.18 | ...22-626,642-643 
  ...le-watcher.ts |   90.42 |    85.36 |      95 |   90.42 | ...92,295-297,342 
  ...resh-state.ts |   90.57 |    97.29 |   93.75 |   90.57 | 133-138,142-148   
  ...ime-reload.ts |     100 |    69.69 |     100 |     100 | ...12-113,122-123 
  hot-reload.ts    |     100 |    89.74 |     100 |     100 | 47,160,220        
  keyBindings.ts   |    97.1 |       50 |     100 |    97.1 | 212-215           
  ...ngsAdapter.ts |     100 |    94.11 |     100 |     100 | 64                
  ...ig-watcher.ts |   95.17 |    83.05 |     100 |   95.17 | ...78,200,292-293 
  mcpApprovals.ts  |   96.55 |    95.65 |     100 |   96.55 | 223-224,229-231   
  mcpJson.ts       |     100 |      100 |     100 |     100 |                   
  mcpServers.ts    |   92.85 |     87.5 |     100 |   92.85 | 46-47             
  ...idersScope.ts |      92 |     90.9 |     100 |      92 | 11-12             
  ...abledTools.ts |     100 |      100 |     100 |     100 |                   
  ...comparison.ts |     100 |      100 |     100 |     100 |                   
  ...n-settings.ts |   99.15 |    93.75 |     100 |   99.15 | 63                
  sandboxConfig.ts |   61.64 |    71.87 |   66.66 |   61.64 | ...54-68,73,77-89 
  ...ings-cache.ts |   96.52 |    93.93 |     100 |   96.52 | 90-91,201-202     
  settings.ts      |   89.79 |    89.44 |   89.65 |   89.79 | ...61,963,965-966 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  ...ngsWatcher.ts |   95.54 |    88.34 |     100 |   95.54 | ...28,277-278,293 
  ...d-env-keys.ts |     100 |      100 |     100 |     100 |                   
  ...paths-lite.ts |   89.47 |       88 |     100 |   89.47 | 43-44,53-54,56-57 
  ...tedFolders.ts |   93.78 |    94.82 |     100 |   93.78 | ...43-344,380-391 
 ...nfig/migration |   95.23 |    78.94 |   83.33 |   95.23 |                   
  index.ts         |   95.65 |    88.88 |     100 |   95.65 | 117-118           
  scheduler.ts     |   96.55 |    77.77 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.91 |      100 |     100 |   94.91 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |      100 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
  v5-to-v4.ts      |      96 |      100 |     100 |      96 | 94-95,99          
 src/core          |     100 |      100 |     100 |     100 |                   
  auth.ts          |     100 |      100 |     100 |     100 |                   
  initializer.ts   |     100 |      100 |     100 |     100 |                   
  theme.ts         |     100 |      100 |     100 |     100 |                   
 src/dualOutput    |   69.39 |    66.66 |   63.15 |   69.39 |                   
  ...tputBridge.ts |   69.48 |     67.3 |    64.7 |   69.48 | ...82-383,391-394 
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/export        |       0 |        0 |       0 |       0 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-7               
 src/generated     |     100 |      100 |     100 |     100 |                   
  git-commit.ts    |     100 |      100 |     100 |     100 |                   
 src/i18n          |   83.22 |       80 |    86.2 |   83.22 |                   
  index.ts         |    65.4 |    76.92 |      80 |    65.4 | ...70-271,294-299 
  languages.ts     |   96.92 |    86.66 |     100 |   96.92 | 134-135,167,184   
  ...nslateKeys.ts |     100 |      100 |     100 |     100 |                   
  ...lationDict.ts |   93.33 |    66.66 |     100 |   93.33 | 15                
 src/i18n/locales  |     100 |      100 |     100 |     100 |                   
  ca.js            |     100 |      100 |     100 |     100 |                   
  de.js            |     100 |      100 |     100 |     100 |                   
  en.js            |     100 |      100 |     100 |     100 |                   
  fr.js            |     100 |      100 |     100 |     100 |                   
  ja.js            |     100 |      100 |     100 |     100 |                   
  pt.js            |     100 |      100 |     100 |     100 |                   
  ru.js            |     100 |      100 |     100 |     100 |                   
  zh-TW.js         |     100 |      100 |     100 |     100 |                   
  zh.js            |     100 |      100 |     100 |     100 |                   
 ...nonInteractive |   78.99 |    76.11 |   81.03 |   78.99 |                   
  session.ts       |   83.18 |       75 |   93.47 |   83.18 | ...63-964,973-983 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...00-601,604-605 
 ...active/control |   75.88 |    88.46 |      80 |   75.88 |                   
  ...rolContext.ts |    6.45 |        0 |       0 |    6.45 | 56-95             
  ...Dispatcher.ts |   91.69 |       92 |   88.88 |   91.69 | ...49-367,384,387 
  ...rolService.ts |     7.4 |        0 |       0 |     7.4 | 46-185            
 ...ol/controllers |   27.87 |    44.44 |    37.5 |   27.87 |                   
  ...Controller.ts |   39.49 |      100 |      80 |   39.49 | 88-92,127-210     
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |   31.32 |     38.7 |      40 |   31.32 | ...68-577,592-597 
  ...Controller.ts |   14.06 |      100 |       0 |   14.06 | ...82-117,130-133 
  ...Controller.ts |   24.52 |       40 |   33.33 |   24.52 | ...64-476,485-514 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |   97.49 |    93.84 |   95.23 |   97.49 |                   
  ...putAdapter.ts |   97.13 |    92.89 |   98.07 |   97.13 | ...1314,1409-1410 
  ...putAdapter.ts |      96 |    91.66 |   85.71 |      96 | 51-52             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.38 |      100 |   90.47 |   98.38 | 83-84,124-125     
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/remoteInput   |   87.31 |    75.32 |   88.23 |   87.31 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.01 |       76 |   93.33 |   88.01 | ...49-350,361-364 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/serve         |   89.27 |    85.66 |   90.39 |   89.27 |                   
  ...ion-bridge.ts |     100 |      100 |     100 |     100 |                   
  auth.ts          |    93.4 |    93.05 |     100 |    93.4 | ...16-317,320-322 
  ...em-adapter.ts |     100 |      100 |     100 |     100 |                   
  capabilities.ts  |     100 |    96.55 |     100 |     100 | 447               
  ...cp-command.ts |     100 |      100 |     100 |     100 |                   
  ...-selection.ts |     100 |      100 |     100 |     100 |                   
  ...worker-env.ts |     100 |      100 |     100 |     100 |                   
  ...supervisor.ts |    97.6 |    89.81 |   95.91 |    97.6 | ...08,530,545,719 
  daemon-logger.ts |   98.31 |    87.32 |   96.55 |   98.31 | 119-120,205       
  ...trics-ring.ts |     100 |      100 |     100 |     100 |                   
  ...s-provider.ts |   68.04 |    52.77 |     100 |   68.04 | ...44-249,282-290 
  daemon-status.ts |    98.1 |    87.92 |     100 |    98.1 | ...47-949,951-952 
  debug-mode.ts    |     100 |      100 |     100 |     100 |                   
  demo.ts          |     100 |      100 |     100 |     100 |                   
  env-snapshot.ts  |    91.3 |       80 |     100 |    91.3 | ...24-127,205-212 
  ...-path-argv.ts |     100 |      100 |     100 |     100 |                   
  ...h-settings.ts |   94.88 |    89.02 |     100 |   94.88 | ...08,683,699,709 
  fast-path.ts     |   91.28 |    81.98 |   95.45 |   91.28 | ...64-473,539-540 
  index.ts         |       0 |        0 |       0 |       0 | 1-143             
  ...e-observer.ts |   89.89 |    83.24 |      96 |   89.89 | ...11-512,541-543 
  ...back-binds.ts |     100 |    88.88 |     100 |     100 | 32                
  ...sion-audit.ts |     100 |      100 |   93.33 |     100 |                   
  rate-limit.ts    |   92.43 |    88.17 |     100 |   92.43 | ...81-283,295-297 
  ...qwen-serve.ts |   83.54 |    81.18 |   71.61 |   83.54 | ...3957,4002-4005 
  ...tup-errors.ts |     100 |      100 |     100 |     100 |                   
  ...-keepalive.ts |    95.8 |    89.24 |     100 |    95.8 | ...16,476-478,514 
  ...-lifecycle.ts |     100 |      100 |     100 |     100 |                   
  server.ts        |   95.38 |    95.77 |      80 |   95.38 | ...1077,1131-1135 
  ...on-helpers.ts |      90 |      100 |     100 |      90 | 14                
  ...t-event-id.ts |     100 |    91.66 |     100 |     100 | 12                
  ...-admission.ts |   97.87 |    88.23 |     100 |   97.87 | 56                
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...erver-name.ts |     100 |      100 |     100 |     100 |                   
  ...ion-limits.ts |     100 |      100 |     100 |     100 |                   
  ...l-resolver.ts |   90.32 |    66.66 |     100 |   90.32 | 16,45-46          
  ...ell-static.ts |   91.07 |     86.2 |     100 |   91.07 | ...79-182,216-219 
  ...ace-agents.ts |   62.47 |    70.34 |   90.47 |   62.47 | ...1332,1342-1352 
  ...ace-inputs.ts |     100 |      100 |     100 |     100 |                   
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...-summaries.ts |   86.66 |       50 |     100 |   86.66 | 11,19             
  ...ace-memory.ts |   87.17 |    78.46 |     100 |   87.17 | ...56-363,423-430 
  ...ers-status.ts |   97.32 |    81.19 |     100 |   97.32 | ...61,164,297-303 
  ...e-registry.ts |   92.95 |     90.9 |     100 |   92.95 | ...11-212,218-219 
  ...ber-errors.ts |     100 |    95.37 |     100 |     100 | 53,93-94,172,192  
  ...e-remember.ts |   97.75 |    93.54 |     100 |   97.75 | ...50,254-259,293 
  ...ls-mapping.ts |     100 |      100 |     100 |     100 |                   
  ...lls-status.ts |     100 |    94.44 |     100 |     100 | 102               
 ...serve/acp-http |   73.28 |    75.79 |    93.5 |   73.28 |                   
  ...r-registry.ts |     100 |    95.45 |     100 |     100 | 191               
  client-mcp-ws.ts |   54.85 |    58.62 |   72.72 |   54.85 | ...99-300,304-305 
  ...n-registry.ts |   97.59 |    88.75 |   97.56 |   97.59 | 1015,1035-1049    
  dispatch.ts      |   67.64 |    71.81 |     100 |   67.64 | ...4344,4392-4398 
  index.ts         |   77.05 |    73.78 |   88.46 |   77.05 | ...1499,1528-1530 
  json-rpc.ts      |     100 |    96.96 |     100 |     100 | 92                
  safe-ws-send.ts  |   52.94 |    71.42 |     100 |   52.94 | 33-42,47-55       
  sse-stream.ts    |   93.96 |    88.57 |   84.61 |   93.96 | ...57-159,161-163 
  ...ort-stream.ts |       0 |        0 |       0 |       0 | 1                 
  ws-stream.ts     |   91.86 |       80 |     100 |   91.86 | 45,50,96,100-103  
 src/serve/auth    |   86.86 |    79.18 |   93.87 |   86.86 |                   
  device-flow.ts   |   96.35 |       80 |   97.61 |   96.35 | ...1358,1453,1519 
  ...w-provider.ts |   44.24 |    74.07 |   71.42 |   44.24 | ...23-284,297,301 
 ...rve/cdp-tunnel |   85.73 |    73.17 |    97.5 |   85.73 |                   
  ...r-emulator.ts |   88.57 |    63.63 |     100 |   88.57 | ...72-175,194-195 
  ...verse-link.ts |      88 |    76.19 |     100 |      88 | ...28-329,420-423 
  ...l-registry.ts |     100 |      100 |     100 |     100 |                   
  cdp-ws.ts        |   76.28 |    61.29 |    87.5 |   76.28 | ...13-217,223-228 
 ...nel/acceptance |       0 |        0 |       0 |       0 |                   
  ...mcp-smoke.mjs |       0 |        0 |       0 |       0 | 1-119             
  ...cceptance.mjs |       0 |        0 |       0 |       0 | 1-473             
  real-tab.mjs     |       0 |        0 |       0 |       0 | 1-218             
 src/serve/fs      |   85.68 |    79.96 |     100 |   85.68 |                   
  audit.ts         |     100 |    96.15 |     100 |     100 | 204               
  errors.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...x-registry.ts |     100 |      100 |     100 |     100 |                   
  paths.ts         |   77.64 |     73.6 |     100 |   77.64 | ...65,594-598,611 
  policy.ts        |   90.32 |    89.18 |     100 |   90.32 | 142-150           
  ...ile-system.ts |   85.19 |    78.44 |     100 |   85.19 | ...2094,2104-2105 
 src/serve/routes  |   83.23 |    79.25 |   90.68 |   83.23 |                   
  a2ui-action.ts   |   99.49 |    94.52 |    87.5 |   99.49 | 250               
  capabilities.ts  |     100 |      100 |     100 |     100 |                   
  daemon-status.ts |   85.18 |    83.33 |     100 |   85.18 | 95-102            
  health-demo.ts   |    92.3 |    83.33 |     100 |    92.3 | 55-59             
  permission.ts    |     100 |     92.3 |     100 |     100 | 50,98             
  ...uled-tasks.ts |   81.05 |    81.91 |     100 |   81.05 | ...33-741,773-774 
  ...on-runtime.ts |   95.71 |    81.81 |     100 |   95.71 | 73-75             
  session.ts       |   82.52 |    81.67 |   89.65 |   82.52 | ...2139,2148-2165 
  sse-events.ts    |   86.25 |     88.6 |   77.77 |   86.25 | ...81,387,404-407 
  usage-stats.ts   |     100 |    95.65 |     100 |     100 | 116               
  ...space-auth.ts |    83.7 |       75 |     100 |    83.7 | ...23,328,340-344 
  ...extensions.ts |   84.51 |    72.96 |   86.36 |   84.51 | ...68-970,989-992 
  ...-file-read.ts |   94.41 |    76.92 |     100 |   94.41 | ...28-329,390-392 
  ...file-write.ts |    82.1 |    60.52 |     100 |    82.1 | ...42-244,247-249 
  ...-lifecycle.ts |     100 |    91.66 |     100 |     100 | 65                
  ...cp-control.ts |   71.42 |    83.72 |     100 |   71.42 | ...04-210,215-218 
  ...ermissions.ts |   87.38 |    76.19 |     100 |   87.38 | ...05-106,132-133 
  ...e-settings.ts |   43.26 |       55 |   66.66 |   43.26 | ...71-281,286-291 
  ...tup-github.ts |   77.52 |    70.27 |   84.21 |   77.52 | ...87,309,352-353 
  ...ace-status.ts |   77.21 |    66.66 |     100 |   77.21 | ...88-189,198-199 
  ...pace-tools.ts |   77.77 |       75 |     100 |   77.77 | 47-52,62-67,91-94 
  ...pace-trust.ts |   76.84 |    72.22 |      50 |   76.84 | ...,63-64,117-118 
  ...pace-voice.ts |   87.09 |     82.6 |    90.9 |   87.09 | ...96,322-323,452 
 src/serve/server  |   87.34 |    87.33 |   93.93 |   87.34 |                   
  access-log.ts    |   97.72 |    95.45 |     100 |   97.72 | 51                
  ...er-helpers.ts |   63.82 |    77.96 |   81.81 |   63.82 | ...16,330,332-347 
  ...w-registry.ts |   98.38 |    88.23 |     100 |   98.38 | 81                
  ...r-handlers.ts |   81.81 |       50 |     100 |   81.81 | 16,23,27-28       
  ...r-response.ts |   80.87 |    73.04 |     100 |   80.87 | ...99-523,582-591 
  fs-factory.ts    |     100 |    92.59 |     100 |     100 | 33,41,100,156     
  ...t-deadline.ts |     100 |      100 |     100 |     100 |                   
  ...iter-setup.ts |   66.66 |    66.66 |   33.33 |   66.66 | 28-33,36-41       
  ...st-helpers.ts |   93.97 |    93.61 |     100 |   93.97 | ...31-133,387-392 
  self-origin.ts   |   76.19 |       80 |     100 |   76.19 | 45-54             
  ...e-features.ts |   92.55 |     91.3 |     100 |   92.55 | 120-126           
  ...on-archive.ts |    89.1 |    89.13 |    87.5 |    89.1 | ...13-418,490-500 
  ...ion-export.ts |     100 |    84.61 |     100 |     100 | 60,84             
  session-list.ts  |   94.19 |    90.38 |     100 |   94.19 | ...37-352,370-376 
  telemetry.ts     |   96.34 |     97.2 |     100 |   96.34 | ...54-156,246-248 
 src/serve/voice   |   79.61 |    89.42 |    82.6 |   79.61 |                   
  ...ice-config.ts |   96.77 |       30 |     100 |   96.77 | 85-86             
  voice-ws.ts      |   76.61 |    95.74 |      80 |   76.61 | ...94,409,447-449 
 ...kspace-service |   93.05 |    80.64 |   96.77 |   93.05 |                   
  index.ts         |   92.83 |    80.32 |   96.42 |   92.83 | ...02,925-929,932 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services      |   91.92 |    88.47 |   98.24 |   91.92 |                   
  ...mandLoader.ts |     100 |    88.88 |     100 |     100 | 102-115           
  ...killLoader.ts |     100 |    93.33 |     100 |     100 | 48,67             
  ...andService.ts |   98.73 |      100 |     100 |   98.73 | 107               
  ...mandLoader.ts |   86.83 |    83.87 |     100 |   86.83 | ...30-335,340-345 
  ...omptLoader.ts |   75.84 |    80.64 |   83.33 |   75.84 | ...10-211,277-278 
  ...mandLoader.ts |     100 |    97.36 |     100 |     100 | 66                
  ...nd-factory.ts |   91.42 |    91.66 |     100 |   91.42 | 128,137-144       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  ...ndMetadata.ts |   98.23 |    96.72 |     100 |   98.23 | 83,87             
  commandUtils.ts  |      96 |     90.9 |     100 |      96 | 48                
  ...and-parser.ts |   90.69 |    85.71 |     100 |   90.69 | 63-66             
  ...ionService.ts |     100 |      100 |     100 |     100 |                   
  ...low-loader.ts |     100 |    96.15 |     100 |     100 | 88                
  setup-github.ts  |    90.5 |    81.81 |     100 |    90.5 | ...35-436,443-444 
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |   98.64 |    95.71 |     100 |   98.64 | 116,142-143       
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  voice-service.ts |   85.99 |    87.09 |     100 |   85.99 | ...68,275,329-343 
  ...e-settings.ts |     100 |    95.45 |     100 |     100 | 19                
  ...ranscriber.ts |   90.45 |    82.08 |   95.83 |   90.45 | ...32-634,637-639 
 ...ght/generators |    88.3 |    85.49 |   92.59 |    88.3 |                   
  DataProcessor.ts |   88.22 |    85.48 |      95 |   88.22 | ...1341,1345-1352 
  ...tGenerator.ts |   98.21 |    85.71 |     100 |   98.21 | 46                
  ...teRenderer.ts |   45.45 |      100 |       0 |   45.45 | 13-51             
 .../insight/types |       0 |       50 |      50 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 | 1                 
 ...mpt-processors |   97.27 |    94.04 |     100 |   97.27 |                   
  ...tProcessor.ts |     100 |      100 |     100 |     100 |                   
  ...eProcessor.ts |   94.52 |    84.21 |     100 |   94.52 | 46-47,93-94       
  ...tionParser.ts |     100 |      100 |     100 |     100 |                   
  ...lProcessor.ts |   97.41 |    95.65 |     100 |   97.41 | 95-98             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.35 |    84.84 |     100 |   97.35 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  tipHistory.ts    |   92.59 |       70 |     100 |   92.59 | ...24,146,153,162 
  tipRegistry.ts   |     100 |      100 |     100 |     100 |                   
  tipScheduler.ts  |     100 |    91.66 |     100 |     100 | 55                
 src/startup       |    87.6 |    82.82 |      90 |    87.6 |                   
  ...p-prefetch.ts |   99.27 |    97.22 |   85.71 |   99.27 | 44                
  ...reeStartup.ts |   80.53 |     74.6 |     100 |   80.53 | ...94,403,409-412 
 src/test-utils    |   94.04 |    83.33 |      80 |   94.04 |                   
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   69.72 |    71.35 |   62.33 |   69.72 |                   
  App.tsx          |   33.33 |       75 |   33.33 |   33.33 | 32-86             
  AppContainer.tsx |   70.92 |    67.19 |   61.53 |   70.92 | ...3776,4294-4298 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |   29.23 |      100 |       0 |   29.23 | 25-75             
  ...tionNudge.tsx |    7.69 |      100 |       0 |    7.69 | 25-103            
  colors.ts        |      60 |      100 |   35.29 |      60 | ...52,54-55,60-61 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  keyMatchers.ts   |   95.91 |    97.14 |     100 |   95.91 | 25-26             
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  ...ractiveUI.tsx |   57.47 |    31.57 |      40 |   57.47 | ...31-232,237-242 
  ...inePresets.ts |   96.27 |    83.87 |     100 |   96.27 | ...97,402,410-412 
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/auth       |   58.45 |    66.18 |   51.06 |   58.45 |                   
  AuthDialog.tsx   |   59.01 |     42.1 |   16.66 |   59.01 | ...25,332-354,358 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  ...etupSteps.tsx |   60.03 |    70.73 |   57.69 |   60.03 | ...87,791,800,803 
  useAuth.ts       |    94.6 |    73.52 |     100 |    94.6 | ...21-222,241-247 
  ...rSetupFlow.ts |   43.18 |    33.33 |      50 |   43.18 | ...78-399,416-459 
 src/ui/commands   |   79.89 |    82.61 |   88.39 |   79.89 |                   
  aboutCommand.ts  |     100 |      100 |     100 |     100 |                   
  agentsCommand.ts |   83.78 |      100 |      60 |   83.78 | 30-32,42-44       
  ...odeCommand.ts |   89.47 |    81.25 |     100 |   89.47 | 92-93,95-100      
  arenaCommand.ts  |   62.81 |    58.73 |   65.21 |   62.81 | ...90-595,680-688 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  branchCommand.ts |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |   94.32 |    77.41 |     100 |   94.32 | 35-36,114-119     
  bugCommand.ts    |     100 |    77.77 |     100 |     100 | 27,61             
  cdCommand.ts     |    92.1 |     84.9 |     100 |    92.1 | ...4-69,94-99,178 
  clearCommand.ts  |    80.9 |    70.83 |     100 |    80.9 | ...24-125,133-142 
  ...essCommand.ts |   67.95 |    55.88 |      75 |   67.95 | ...86-187,201-204 
  ...astCommand.ts |   70.86 |    74.07 |      75 |   70.86 | ...,61-93,117-122 
  ...ig-command.ts |   93.12 |    88.42 |     100 |   93.12 | ...07-315,321-323 
  ...extCommand.ts |   67.46 |    69.69 |   84.61 |   67.46 | ...53-586,597-598 
  copyCommand.ts   |   98.49 |    95.78 |     100 |   98.49 | ...80,280,321,327 
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  diffCommand.ts   |     100 |     87.5 |     100 |     100 | ...61,224-225,238 
  ...ryCommand.tsx |   81.43 |     88.4 |    90.9 |   81.43 | ...59-264,311-318 
  docsCommand.ts   |     100 |     90.9 |     100 |     100 | 25                
  doctorCommand.ts |   65.37 |    81.88 |   94.11 |   65.37 | ...85-535,538-672 
  dreamCommand.ts  |   85.45 |    88.88 |     100 |   85.45 | 58-65             
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  ...rt-command.ts |   82.97 |    78.57 |     100 |   82.97 | 47-52,67-70,91-96 
  exportCommand.ts |   98.25 |    91.02 |     100 |   98.25 | ...81,198-199,364 
  ...onsCommand.ts |    50.3 |    48.14 |   69.23 |    50.3 | ...08,262-314,375 
  forgetCommand.ts |     100 |       90 |     100 |     100 | 59                
  forkCommand.ts   |     100 |    94.11 |     100 |     100 | 96,147            
  goalCommand.ts   |   91.46 |    84.44 |      90 |   91.46 | ...87-190,202-205 
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  ...oryCommand.ts |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |   81.13 |    65.71 |   85.71 |   81.13 | ...,86-93,131-132 
  ideCommand.ts    |   60.75 |    64.28 |   41.17 |   60.75 | ...05-306,310-324 
  ...figCommand.ts |   52.83 |    81.25 |      70 |   52.83 | ...74-319,321-330 
  initCommand.ts   |   84.33 |    72.72 |     100 |   84.33 | 68,82-87,89-94    
  ...ghtCommand.ts |   77.87 |    71.42 |     100 |   77.87 | ...44-245,250-272 
  ...ageCommand.ts |   92.17 |    82.69 |     100 |   92.17 | ...39,159,168-178 
  lspCommand.ts    |     100 |    86.95 |     100 |     100 | 31,101-102        
  mcpCommand.ts    |     100 |      100 |     100 |     100 |                   
  memoryCommand.ts |     100 |      100 |     100 |     100 |                   
  modelCommand.ts  |    81.1 |     89.5 |   88.23 |    81.1 | ...80-793,827-832 
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |     100 |      100 |     100 |     100 |                   
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...ns-command.ts |   98.83 |    81.81 |     100 |   98.83 | 100               
  ...berCommand.ts |     100 |     87.5 |     100 |     100 | 46                
  renameCommand.ts |   85.71 |    86.04 |     100 |   85.71 | ...02-209,216-221 
  ...oreCommand.ts |    90.9 |    86.04 |     100 |    90.9 | ...41-146,176-177 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |   81.25 |      100 |      50 |   81.25 | 20-22             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   89.47 |       75 |      80 |   89.47 | 54-59             
  skillsCommand.ts |   78.82 |    81.81 |     100 |   78.82 | 37-52,78,97       
  statsCommand.ts  |    90.6 |    77.95 |     100 |    90.6 | ...91-694,785-792 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |    6.43 |      100 |      50 |    6.43 | 31-330            
  tasksCommand.ts  |   77.22 |    72.13 |     100 |   77.22 | ...46-150,172-177 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |     100 |      100 |     100 |     100 |                   
  trustCommand.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  vimCommand.ts    |   54.54 |      100 |      50 |   54.54 | 19-29             
  voice-command.ts |   93.57 |       88 |     100 |   93.57 | 35,97-102         
  ...owsCommand.ts |   91.82 |    78.87 |   66.66 |   91.82 | ...59-160,169-174 
 src/ui/components |   68.09 |     78.5 |   74.23 |   68.09 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  ...ateScreen.tsx |   77.41 |    66.66 |      50 |   77.41 | 34-40             
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |       0 |        0 |       0 |       0 | 1-97              
  AppHeader.tsx    |    88.7 |       75 |     100 |    88.7 | 36,38-43,45       
  ...odeDialog.tsx |   87.24 |    72.22 |   33.33 |   87.24 | ...85,233-238,245 
  AsciiArt.ts      |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |   95.65 |    66.66 |     100 |   95.65 | 27,52             
  ...TextInput.tsx |   86.72 |    86.66 |     100 |   86.72 | ...00-302,355-359 
  Composer.tsx     |   94.39 |    66.66 |     100 |   94.39 | ...-71,83,138,151 
  ...entPrompt.tsx |     100 |      100 |     100 |     100 |                   
  ...ryDisplay.tsx |   75.89 |    62.06 |     100 |   75.89 | ...,88,93-108,113 
  ...geDisplay.tsx |   68.42 |    57.14 |     100 |   68.42 | 16-17,31-32,42-50 
  CronPill.tsx     |     100 |    93.75 |     100 |     100 | 18                
  ...ification.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |       0 |        0 |       0 |       0 | 1-596             
  DiffDialog.tsx   |   31.17 |    19.51 |   30.76 |   31.17 | ...07-712,722-735 
  ...ngsDialog.tsx |       0 |        0 |       0 |       0 | 1-195             
  EffortDialog.tsx |   97.36 |      100 |     100 |   97.36 | 55-56             
  ExitWarning.tsx  |     100 |      100 |     100 |     100 |                   
  ...hProgress.tsx |    87.8 |    33.33 |     100 |    87.8 | 28-31,56          
  ...ustDialog.tsx |     100 |      100 |     100 |     100 |                   
  Footer.tsx       |    77.6 |    61.11 |     100 |    77.6 | ...17-222,240-244 
  ...ngSpinner.tsx |   68.42 |    85.71 |      50 |   68.42 | 35-52,73,80-81    
  GoalPill.tsx     |   83.33 |    76.92 |     100 |   83.33 | 24-30             
  Header.tsx       |   98.65 |    94.73 |     100 |   98.65 | 173,175           
  Help.tsx         |   98.32 |       90 |     100 |   98.32 | ...24,381,447-448 
  ...emDisplay.tsx |   73.66 |    64.19 |     100 |   73.66 | ...46,449,452-458 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |   81.76 |    80.27 |   77.77 |   81.76 | ...2074,2100,2160 
  ...Shortcuts.tsx |   20.87 |      100 |       0 |   20.87 | ...6,49-51,67-125 
  ...Indicator.tsx |   98.18 |    97.82 |     100 |   98.18 | 161-162           
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |   98.16 |    94.82 |   66.66 |   98.16 | 89,166-170,471    
  MemoryDialog.tsx |   86.59 |    80.15 |     100 |   86.59 | ...34-435,485,553 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   86.41 |       73 |     100 |   86.41 | ...74,876,881-897 
  ...tsDisplay.tsx |     100 |    97.22 |     100 |     100 | 270               
  ...fications.tsx |       0 |        0 |       0 |       0 | 1-58              
  ...onsDialog.tsx |       0 |        0 |       0 |       0 | 1-1004            
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...icePrompt.tsx |   92.64 |    85.71 |     100 |   92.64 | 102-106,134-139   
  PrepareLabel.tsx |   91.66 |    77.27 |     100 |   91.66 | 73-75,77-79,110   
  ...atePrompt.tsx |       0 |        0 |       0 |       0 | 1-134             
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |       0 |        0 |       0 |       0 | 1-39              
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...dSelector.tsx |   92.79 |    82.65 |     100 |   92.79 | ...19-323,354-370 
  ...ionPicker.tsx |   83.66 |    72.13 |     100 |   83.66 | ...96,402,444-466 
  ...onPreview.tsx |   93.58 |    84.21 |     100 |   93.58 | ...,70-71,195-197 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   72.56 |       80 |      40 |   72.56 | ...06-109,114-117 
  ...tedDialog.tsx |     100 |      100 |     100 |     100 |                   
  ...ngsDialog.tsx |   71.49 |    74.08 |   69.23 |   71.49 | ...1252,1258-1259 
  ...ionDialog.tsx |    92.3 |    96.15 |   33.33 |    92.3 | 60-63,68-75,164   
  ...putPrompt.tsx |    15.9 |      100 |       0 |    15.9 | 20-63             
  ...Indicator.tsx |   57.14 |      100 |       0 |   57.14 | 12-15             
  ...MoreLines.tsx |       0 |        0 |       0 |       0 | 1-40              
  ...iewDialog.tsx |   97.78 |    87.67 |     100 |   97.78 | ...97,305-307,324 
  ...tsDisplay.tsx |   95.86 |       75 |     100 |   95.86 | 67-71             
  ...ionPicker.tsx |       0 |        0 |       0 |       0 | 1-172             
  ...tivityTab.tsx |    3.94 |      100 |       0 |    3.94 | 27-275            
  StatsDialog.tsx  |    8.64 |      100 |       0 |    8.64 | ...76-111,130-322 
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ciencyTab.tsx |    78.9 |    56.52 |     100 |    78.9 | ...26,213,262-288 
  ...atmapView.tsx |    8.98 |      100 |       0 |    8.98 | 20-107            
  ...essionTab.tsx |    5.46 |      100 |       0 |    5.46 | 24-215            
  ...ineDialog.tsx |    93.5 |    85.18 |     100 |    93.5 | ...05,267,287-289 
  ...yTodoList.tsx |   96.33 |    88.23 |     100 |   96.33 | 137-140           
  ...nsDisplay.tsx |    92.9 |       85 |     100 |    92.9 | ...04,207,234-236 
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  ...ingViewer.tsx |   74.01 |    55.55 |     100 |   74.01 | ...02,111-118,130 
  Tips.tsx         |   93.54 |       75 |     100 |   93.54 | 39-40             
  TodoDisplay.tsx  |     100 |      100 |     100 |     100 |                   
  ...tsDisplay.tsx |     100 |     87.5 |     100 |     100 | 31-32             
  TrustDialog.tsx  |     100 |    81.81 |     100 |     100 | 71-86             
  ...ification.tsx |       0 |        0 |       0 |       0 | 1-22              
  ...Indicator.tsx |    92.5 |     87.5 |     100 |    92.5 | 50-53             
  ...ackDialog.tsx |       0 |        0 |       0 |       0 | 1-134             
  ...xitDialog.tsx |   80.36 |    43.47 |      60 |   80.36 | ...24-238,248-251 
  ...odeVisuals.ts |   97.22 |    85.71 |     100 |   97.22 | 25                
  ...s-helpers.tsx |   66.25 |    81.25 |      50 |   66.25 | 25-32,46-53,62-72 
 ...nts/agent-view |   53.72 |    70.87 |   42.85 |   53.72 |                   
  ...atContent.tsx |    9.09 |      100 |       0 |    9.09 | 54-275,281-283    
  ...tChatView.tsx |   21.05 |      100 |       0 |   21.05 | 21-39             
  ...tComposer.tsx |   64.78 |    29.41 |   33.33 |   64.78 | ...51,269,277-279 
  AgentFooter.tsx  |   15.38 |      100 |       0 |   15.38 | 28-65             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    87.9 |    63.88 |     100 |    87.9 | ...88,110-118,136 
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |   42.38 |    68.69 |   73.68 |   42.38 |                   
  ArenaCards.tsx   |   73.06 |    71.79 |   85.71 |   73.06 | ...83-185,321-326 
  ...ectDialog.tsx |   83.48 |    69.86 |   88.88 |   83.48 | ...88-392,409-410 
  ...artDialog.tsx |       0 |        0 |       0 |       0 | 1-164             
  ...tusDialog.tsx |       0 |        0 |       0 |       0 | 1-288             
  ...topDialog.tsx |       0 |        0 |       0 |       0 | 1-213             
 ...ackground-view |   81.04 |    80.44 |   90.56 |   81.04 |                   
  ...sksDialog.tsx |   75.72 |    75.15 |      80 |   75.72 | ...1687,1709-1715 
  ...TasksPill.tsx |   67.03 |     86.2 |     100 |   67.03 | ...02-122,130-138 
  ...gentPanel.tsx |   97.14 |    86.45 |     100 |   97.14 | 132,455-459,527   
  agent-forest.ts  |    99.2 |    93.93 |     100 |    99.2 | 256               
  ...Visibility.ts |     100 |      100 |     100 |     100 |                   
  ...e-overlay.tsx |    88.2 |    76.47 |     100 |    88.2 | ...36-138,140-142 
 ...nts/extensions |   84.58 |    78.18 |   83.33 |   84.58 |                   
  ...gerDialog.tsx |   82.46 |    77.77 |     100 |   82.46 | ...89,191-198,258 
  TabBar.tsx       |   97.29 |    88.88 |     100 |   97.29 | 33                
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   46.15 |    84.74 |   58.82 |   46.15 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |       0 |        0 |       0 |       0 | 1-145             
  ...nListStep.tsx |   75.13 |    88.09 |   66.66 |   75.13 | ...52,173,202-208 
  ...electStep.tsx |       0 |        0 |       0 |       0 | 1-83              
  ...nfirmStep.tsx |   16.32 |      100 |       0 |   16.32 | 28-74             
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
 ...xtensions/tabs |   66.18 |    67.65 |   66.66 |   66.18 |                   
  DiscoverTab.tsx  |   57.21 |     63.2 |   55.55 |   57.21 | ...98,661-665,669 
  InstalledTab.tsx |   71.62 |    68.65 |   83.33 |   71.62 | ...68,773-774,811 
  SourcesTab.tsx   |   69.25 |     70.4 |   66.66 |   69.25 | ...16,535,607-619 
 ...tensions/views |   23.73 |    44.82 |       5 |   23.73 |                   
  ...tionsView.tsx |    6.02 |      100 |       0 |    6.02 | 52-65,68-368      
  ...tionsView.tsx |   43.45 |    44.82 |    6.66 |   43.45 | ...98-405,408-420 
  ...etailView.tsx |    9.56 |      100 |       0 |    9.56 | 40-67,70-158      
 ...mponents/hooks |   86.85 |    81.37 |   91.89 |   86.85 |                   
  ...rListBody.tsx |   95.29 |    85.18 |     100 |   95.29 | 95-98             
  ...etailStep.tsx |   75.32 |    71.42 |      60 |   75.32 | ...56-169,173-186 
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entHeader.tsx |     100 |    85.71 |     100 |     100 | 47                
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   72.29 |    70.49 |     100 |   72.29 | ...51,563-568,572 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  ...erGrouping.ts |     100 |      100 |     100 |     100 |                   
  sourceLabels.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |   40.96 |    61.53 |   70.58 |   40.96 |                   
  ...ealthPill.tsx |   68.42 |    85.71 |     100 |   68.42 | 40-46             
  ...entDialog.tsx |      33 |    26.19 |      40 |      33 | ...81,883,896-902 
  ...valDialog.tsx |   15.06 |      100 |       0 |   15.06 | 40-109            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-35              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |      97 |       95 |     100 |      97 | 24,113-114        
 ...ents/mcp/steps |   54.25 |    74.31 |   57.14 |   54.25 |                   
  ...icateStep.tsx |    5.65 |      100 |       0 |    5.65 | 40-66,69-308      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...eListStep.tsx |   99.09 |    97.36 |     100 |   99.09 | 71                
  ...etailStep.tsx |    64.8 |    62.26 |   33.33 |    64.8 | ...75-284,295-317 
  ...rListStep.tsx |   88.46 |    81.25 |     100 |   88.46 | ...63,169,174-179 
  ...etailStep.tsx |    10.3 |      100 |       0 |    10.3 | ...1,67-79,82-140 
  ToolListStep.tsx |   69.29 |       50 |     100 |   69.29 | ...23,126,135-144 
 ...nents/messages |   87.26 |    83.75 |   80.68 |   87.26 |                   
  ...ionDialog.tsx |   86.88 |    81.25 |      80 |   86.88 | ...21,539,557-559 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |     100 |    91.52 |     100 |     100 | 27,31,33,35,215   
  ...onMessage.tsx |   91.93 |    82.35 |     100 |   91.93 | 57-59,61,63       
  ...nMessages.tsx |   89.92 |    86.66 |    90.9 |   89.92 | ...13-319,376-382 
  DiffRenderer.tsx |   93.19 |    86.17 |     100 |   93.19 | ...09,237-238,304 
  ...tsDisplay.tsx |   97.82 |    77.27 |     100 |   97.82 | 87,89             
  ...usMessage.tsx |   76.31 |     42.1 |   66.66 |   76.31 | ...99,101,124,155 
  ...tsDisplay.tsx |    95.6 |    88.46 |     100 |    95.6 | ...48,150,183-188 
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   16.66 |      100 |       0 |   16.66 | 22-38             
  ...sMessages.tsx |   58.65 |       50 |    37.5 |   58.65 | ...20-125,146-158 
  ...ryMessage.tsx |   14.28 |      100 |       0 |   14.28 | 23-62             
  ...onMessage.tsx |   82.31 |    74.02 |   33.33 |   82.31 | ...69-471,478-480 
  ...upMessage.tsx |   98.23 |       95 |     100 |   98.23 | 173-176,390       
  ToolMessage.tsx  |   90.79 |    81.87 |    92.3 |   90.79 | ...88-793,820-822 
 ...ponents/shared |   85.52 |    81.56 |   94.62 |   85.52 |                   
  ...ctionList.tsx |     100 |      100 |      75 |     100 |                   
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  EnumSelector.tsx |     100 |    96.42 |     100 |     100 | 58                
  MaxSizedBox.tsx  |   83.01 |    86.15 |   88.88 |   83.01 | ...12-513,618-619 
  MultiSelect.tsx  |   93.58 |       75 |     100 |   93.58 | ...43,199-201,211 
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...ontroller.tsx |     100 |    83.33 |     100 |     100 | 73,93-95          
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  ...lableList.tsx |    82.7 |    84.84 |     100 |    82.7 | 46-64,71-74       
  StaticRender.tsx |   72.72 |      100 |     100 |   72.72 | 31-33             
  TextInput.tsx    |    80.8 |    67.24 |      80 |    80.8 | ...36-240,252-258 
  ...ontroller.tsx |     100 |    81.81 |     100 |     100 | 59-62             
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  ...lizedList.tsx |   88.54 |    83.64 |      90 |   88.54 | ...15,745-773,869 
  text-buffer.ts   |   85.94 |    81.73 |   97.91 |   85.94 | ...2651,2749-2750 
  ...er-actions.ts |   73.93 |    67.22 |     100 |   73.93 | ...32-733,934-936 
 ...ponents/skills |       0 |        0 |       0 |       0 |                   
  ...gerDialog.tsx |       0 |        0 |       0 |       0 | 1-678             
 ...ents/subagents |       0 |        0 |       0 |       0 |                   
  constants.ts     |       0 |        0 |       0 |       0 | 1-71              
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
  reducers.tsx     |       0 |        0 |       0 |       0 | 1-190             
  types.ts         |       0 |        0 |       0 |       0 | 1-125             
  utils.ts         |       0 |        0 |       0 |       0 | 1-102             
 ...bagents/create |       0 |        0 |       0 |       0 |                   
  ...ionWizard.tsx |       0 |        0 |       0 |       0 | 1-299             
  ...rSelector.tsx |       0 |        0 |       0 |       0 | 1-85              
  ...onSummary.tsx |       0 |        0 |       0 |       0 | 1-331             
  ...tionInput.tsx |       0 |        0 |       0 |       0 | 1-177             
  ...dSelector.tsx |       0 |        0 |       0 |       0 | 1-63              
  ...nSelector.tsx |       0 |        0 |       0 |       0 | 1-58              
  ...EntryStep.tsx |       0 |        0 |       0 |       0 | 1-78              
  ToolSelector.tsx |       0 |        0 |       0 |       0 | 1-253             
 ...bagents/manage |   14.04 |    53.19 |    37.5 |   14.04 |                   
  ...ctionStep.tsx |       0 |        0 |       0 |       0 | 1-103             
  ...eleteStep.tsx |       0 |        0 |       0 |       0 | 1-62              
  ...tEditStep.tsx |       0 |        0 |       0 |       0 | 1-124             
  ...ctionStep.tsx |   35.42 |    59.52 |     100 |   35.42 | ...20-432,437-439 
  ...iewerStep.tsx |       0 |        0 |       0 |       0 | 1-73              
  ...gerDialog.tsx |       0 |        0 |       0 |       0 | 1-341             
 ...mponents/views |   69.77 |    72.38 |   61.11 |   69.77 |                   
  ContextUsage.tsx |   70.88 |    63.88 |      80 |   70.88 | ...20-426,463-557 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   87.87 |    73.68 |     100 |   87.87 | 69-76             
  McpStatus.tsx    |   92.01 |     73.8 |     100 |   92.01 | ...36,175-177,262 
  SkillsList.tsx   |   20.51 |      100 |       0 |   20.51 | 17-20,27-57       
  ToolsList.tsx    |     100 |      100 |     100 |     100 |                   
 src/ui/contexts   |   81.99 |    79.01 |   83.33 |   81.99 |                   
  ...ewContext.tsx |   64.83 |    88.88 |      50 |   64.83 | ...16-219,225-235 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |   92.45 |    62.79 |      50 |   92.45 | ...69-270,272-276 
  ...deContext.tsx |   88.88 |      100 |       0 |   88.88 | 21                
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   82.72 |    82.81 |     100 |   82.72 | ...1284,1292-1294 
  ...owContext.tsx |   91.07 |    81.81 |     100 |   91.07 | 47-48,60-62       
  ...deContext.tsx |     100 |      100 |      50 |     100 |                   
  ...onContext.tsx |   78.68 |    73.77 |   91.66 |   78.68 | ...86-389,398-401 
  ...gsContext.tsx |     100 |      100 |     100 |     100 |                   
  ...usContext.tsx |     100 |      100 |     100 |     100 |                   
  ...ngContext.tsx |   71.42 |       50 |     100 |   71.42 | 17-20             
  ...utContext.tsx |   85.71 |      100 |   66.66 |   85.71 | 13-14             
  ...erContext.tsx |     100 |      100 |      50 |     100 |                   
  ...edContext.tsx |     100 |      100 |     100 |     100 |                   
  ...nsContext.tsx |   88.88 |       50 |     100 |   88.88 | 147-148           
  ...teContext.tsx |   86.66 |       50 |     100 |   86.66 | 231-232           
  ...deContext.tsx |      80 |     87.5 |      75 |      80 | ...11-112,118-120 
 src/ui/daemon     |   88.27 |    73.27 |   95.45 |   88.27 |                   
  ...ui-adapter.ts |   88.27 |    73.27 |   95.45 |   88.27 | ...66,784-785,871 
 src/ui/editors    |       0 |        0 |       0 |       0 |                   
  ...ngsManager.ts |       0 |        0 |       0 |       0 | 1-67              
 src/ui/hooks      |   83.63 |    81.62 |   88.34 |   83.63 |                   
  ...dProcessor.ts |   81.49 |    80.95 |     100 |   81.49 | ...33-734,740-745 
  ...ention-ref.ts |   97.67 |       84 |     100 |   97.67 | 63                
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...esourceRef.ts |     100 |      100 |     100 |     100 |                   
  ...dProcessor.ts |   94.62 |    73.58 |     100 |   94.62 | ...86-287,292-293 
  ...dProcessor.ts |   85.14 |    66.27 |   81.81 |   85.14 | ...1400,1421-1425 
  ...rt-command.ts |     100 |      100 |     100 |     100 |                   
  ...sced-flush.ts |     100 |      100 |     100 |     100 |                   
  ...oice-input.ts |   92.36 |    81.95 |   66.66 |   92.36 | ...00,502-503,658 
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-157            
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...dScrollbar.ts |     100 |      100 |     100 |     100 |                   
  ...ationFrame.ts |      42 |       75 |     100 |      42 | 42-44,53-59,62-87 
  ...odeCommand.ts |   58.82 |      100 |     100 |   58.82 | 28,33-48          
  ...enaCommand.ts |      85 |      100 |     100 |      85 | 23-24,29          
  ...aInProcess.ts |   27.92 |       80 |      25 |   27.92 | ...69-170,173-175 
  ...Completion.ts |   89.83 |    88.97 |     100 |   89.83 | ...49-456,496-505 
  ...ifications.ts |   86.91 |    96.29 |     100 |   86.91 | 116-130           
  ...tIndicator.ts |   83.49 |    70.96 |     100 |   83.49 | ...58,166,168-176 
  ...waySummary.ts |   96.22 |    69.69 |     100 |   96.22 | 125-127,169       
  ...ndTaskView.ts |   94.73 |    76.59 |     100 |   94.73 | 162-166,255,261   
  ...chedScroll.ts |     100 |      100 |     100 |     100 |                   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...nchCommand.ts |   93.37 |     73.8 |     100 |   93.37 | ...37,186,259-262 
  ...ompletion.tsx |   96.75 |    81.81 |     100 |   96.75 | ...78-279,289-290 
  ...dMigration.ts |    92.1 |    88.88 |     100 |    92.1 | 42-44             
  useCompletion.ts |   94.11 |    89.65 |     100 |   94.11 | ...32-133,137-138 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   78.26 |       50 |     100 |   78.26 | ...2,75-79,96-104 
  ...eteCommand.ts |   78.53 |    88.57 |     100 |   78.53 | ...96-104,112-113 
  ...ialogClose.ts |   36.11 |       10 |     100 |   36.11 | ...89-195,202-207 
  useDiffData.ts   |       0 |        0 |       0 |       0 | 1-87              
  ...oublePress.ts |   53.12 |       75 |     100 |   53.12 | 33-35,41-54       
  ...orSettings.ts |     100 |      100 |     100 |     100 |                   
  ...Completion.ts |   99.12 |     97.7 |     100 |   99.12 | 182-183           
  ...ionUpdates.ts |    93.5 |     92.3 |     100 |    93.5 | ...87-291,304-310 
  ...agerDialog.ts |   88.88 |      100 |     100 |   88.88 | 21,25             
  ...backDialog.ts |    63.9 |    76.47 |   66.66 |    63.9 | ...66-168,190-191 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |      100 |     100 |     100 |                   
  ...ggestions.tsx |   96.47 |    78.94 |     100 |   96.47 | 121,155-156       
  ...miniStream.ts |   83.35 |     80.2 |   95.65 |   83.35 | ...3584,3669-3677 
  ...BranchName.ts |     100 |    91.66 |     100 |     100 | 30                
  ...oryManager.ts |   97.94 |    98.24 |     100 |   97.94 | 139-142           
  ...ooksDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...stListener.ts |     100 |      100 |     100 |     100 |                   
  ...nAuthError.ts |   76.19 |       50 |     100 |   76.19 | 39-40,43-45       
  ...putHistory.ts |   92.59 |    85.71 |     100 |   92.59 | 63-64,72,94-96    
  ...storyStore.ts |     100 |    94.11 |     100 |     100 | 69                
  useKeypress.ts   |     100 |      100 |     100 |     100 |                   
  ...rdProtocol.ts |   36.36 |      100 |       0 |   36.36 | 24-31             
  ...unchEditor.ts |       0 |        0 |       0 |       0 | 1-90              
  ...gIndicator.ts |     100 |    96.66 |     100 |     100 | 109               
  useLogger.ts     |      16 |      100 |       0 |      16 | 15-45             
  useMCPHealth.ts  |   63.15 |       80 |      50 |   63.15 | 42-52,64-67       
  ...cpApproval.ts |   93.12 |    86.11 |     100 |   93.12 | ...24-127,139-140 
  useMcpDialog.ts  |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...moryDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...oryMonitor.ts |   83.14 |    78.57 |     100 |   83.14 | 54-63,74-79       
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...delCommand.ts |     100 |    92.85 |     100 |     100 | 48                
  ...ouseEvents.ts |   94.25 |    97.22 |   83.33 |   94.25 | 76-80             
  ...raseCycler.ts |   84.74 |    76.47 |     100 |   84.74 | ...49,52-53,69-71 
  ...rredEditor.ts |   58.33 |    22.22 |     100 |   58.33 | 23-27,29-33       
  ...derUpdates.ts |   86.95 |    77.41 |   91.66 |   86.95 | ...70,311-323,371 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |   88.54 |    91.83 |     100 |   88.54 | ...73-278,381-391 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...tleRepaint.ts |     100 |      100 |     100 |     100 |                   
  ...umeCommand.ts |   93.66 |    72.41 |     100 |   93.66 | ...17,155,196-201 
  ...ompletion.tsx |   90.67 |    83.33 |     100 |   90.67 | ...02,105,138-141 
  ...ectionList.ts |   97.12 |    96.22 |     100 |   97.12 | ...92-193,247-250 
  ...sionPicker.ts |   92.87 |    90.35 |     100 |   92.87 | ...99-501,503-505 
  ...earchInput.ts |     100 |    97.29 |     100 |     100 | 82                
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   93.28 |    80.95 |     100 |   93.28 | ...96,153-154,164 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-73              
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...Completion.ts |   82.85 |    85.13 |   94.73 |   82.85 | ...78-680,688-724 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  ...tatsDialog.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |   97.13 |    93.33 |     100 |   97.13 | ...78-382,478-485 
  ...eateDialog.ts |   88.23 |      100 |     100 |   88.23 | 14,18             
  ...mInProcess.ts |   27.35 |       80 |      25 |   27.35 | ...82-183,186-188 
  ...tification.ts |     100 |     87.5 |     100 |     100 | 50                
  ...alProgress.ts |   53.06 |       50 |   66.66 |   53.06 | ...53,61-68,79-85 
  ...rminalSize.ts |   76.19 |      100 |      50 |   76.19 | 21-25             
  ...emeCommand.ts |   67.01 |    29.41 |     100 |   67.01 | ...10-111,115-116 
  useTimer.ts      |   97.59 |    94.73 |     100 |   97.59 | 17-18             
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |      100 |     100 |     100 |                   
  useTurnDiffs.ts  |   95.12 |    78.57 |     100 |   95.12 | 133-134,156-157   
  ...elcomeBack.ts |   87.36 |     90.9 |     100 |   87.36 | ...,94-96,114-115 
  ...reeSession.ts |   93.75 |    72.72 |     100 |   93.75 | 47-48,72          
  vim.ts           |      74 |    67.56 |   69.23 |      74 | ...1854-1861,1869 
 src/ui/layouts    |    90.9 |    90.62 |     100 |    90.9 |                   
  ...AppLayout.tsx |   90.72 |       90 |     100 |   90.72 | 57-59,101-106     
  ...AppLayout.tsx |   91.17 |    91.66 |     100 |   91.17 | 70-75             
 src/ui/models     |   80.24 |    79.16 |   71.42 |   80.24 |                   
  ...ableModels.ts |   80.24 |    79.16 |   71.42 |   80.24 | ...,61-71,123-125 
 ...noninteractive |     100 |      100 |    6.66 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |    6.66 |     100 |                   
 src/ui/state      |   94.91 |    81.81 |     100 |   94.91 |                   
  extensions.ts    |   94.91 |    81.81 |     100 |   94.91 | 68-69,88          
 src/ui/themes     |    98.5 |    73.17 |     100 |    98.5 |                   
  ansi-light.ts    |     100 |      100 |     100 |     100 |                   
  ansi.ts          |     100 |      100 |     100 |     100 |                   
  atom-one-dark.ts |     100 |      100 |     100 |     100 |                   
  ayu-light.ts     |     100 |      100 |     100 |     100 |                   
  ayu.ts           |     100 |      100 |     100 |     100 |                   
  color-utils.ts   |   99.23 |    97.05 |     100 |   99.23 | 277-278           
  default-light.ts |     100 |      100 |     100 |     100 |                   
  default.ts       |     100 |      100 |     100 |     100 |                   
  ...inal-theme.ts |   88.59 |    85.96 |     100 |   88.59 | ...57-261,266-270 
  dracula.ts       |     100 |      100 |     100 |     100 |                   
  github-dark.ts   |     100 |      100 |     100 |     100 |                   
  github-light.ts  |     100 |      100 |     100 |     100 |                   
  googlecode.ts    |     100 |      100 |     100 |     100 |                   
  no-color.ts      |     100 |      100 |     100 |     100 |                   
  qwen-dark.ts     |     100 |      100 |     100 |     100 |                   
  qwen-light.ts    |     100 |      100 |     100 |     100 |                   
  ...tic-tokens.ts |     100 |      100 |     100 |     100 |                   
  ...-of-purple.ts |     100 |      100 |     100 |     100 |                   
  theme-manager.ts |   88.68 |    84.52 |     100 |   88.68 | ...83-392,397-398 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   85.02 |    84.34 |   93.76 |   85.02 |                   
  ...Colorizer.tsx |   80.42 |    85.41 |     100 |   80.42 | ...00-201,298-324 
  ...nRenderer.tsx |   68.83 |    70.14 |      50 |   68.83 | ...52-254,274-293 
  ...wnDisplay.tsx |   92.55 |    93.33 |     100 |   92.55 | ...56,896,943-961 
  ...idDiagram.tsx |   87.79 |    95.34 |     100 |   87.79 | 156-179           
  ...eRenderer.tsx |   92.68 |    82.35 |   95.23 |   92.68 | ...34-737,790-795 
  ...odeDisplay.ts |   96.55 |     90.9 |     100 |   96.55 | 34                
  asciiCharts.ts   |   96.77 |    87.62 |     100 |   96.77 | 173-180,281       
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |   51.92 |    72.72 |   91.66 |   51.92 | ...21,624-633,636 
  commandUtils.ts  |    96.1 |    88.77 |     100 |    96.1 | ...73,175-176,320 
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   90.38 |    73.91 |     100 |   90.38 | 23,25,29,31,33    
  formatters.ts    |    95.4 |    98.43 |     100 |    95.4 | 123-126           
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |     100 |      100 |     100 |     100 |                   
  ...gap-notice.ts |     100 |      100 |     100 |     100 |                   
  ...oryMapping.ts |     100 |       95 |     100 |     100 | 44,103            
  historyUtils.ts  |    95.6 |    95.16 |     100 |    95.6 | 96-99             
  input-mouse.ts   |     100 |    85.71 |     100 |     100 | 48,93             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |    8.23 |      100 |       0 |    8.23 | ...31-132,135-136 
  latexRenderer.ts |   94.95 |     73.8 |     100 |   94.95 | ...76-178,184-187 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  list-mouse.ts    |     100 |      100 |     100 |     100 |                   
  ...ightLoader.ts |     100 |       95 |     100 |     100 | 81                
  ...nUtilities.ts |   95.72 |    94.23 |     100 |   95.72 | 90-92,137-138     
  ...t-position.ts |     100 |     87.5 |     100 |     100 | 85                
  ...ToolGroups.ts |   98.65 |    96.72 |     100 |   98.65 | 48-49             
  ...geRenderer.ts |   86.23 |    69.06 |   95.12 |   86.23 | ...1284,1324-1330 
  ...alRenderer.ts |   86.69 |     71.9 |     100 |   86.69 | ...1476,1513-1519 
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  mouse.ts         |   92.85 |    73.77 |     100 |   92.85 | ...38,145,149-152 
  osc8.ts          |   94.84 |    88.74 |     100 |   94.84 | ...57,442,446-447 
  ...red-height.ts |   96.85 |    95.31 |     100 |   96.85 | 71-73,201-203     
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  restoreGoal.ts   |   99.02 |    97.56 |     100 |   99.02 | 106               
  ...storyUtils.ts |   70.31 |    78.15 |   93.75 |   70.31 | ...81-503,633-634 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...evel-label.ts |   77.77 |    66.66 |     100 |   77.77 | 18,22-24          
  ...are-cursor.ts |   89.47 |    85.71 |     100 |   89.47 | 39-44             
  ...ataService.ts |   93.17 |     79.1 |     100 |   93.17 | ...14,227,254-256 
  ...izedOutput.ts |   94.94 |      100 |   88.88 |   94.94 | 112-117           
  ...wOptimizer.ts |     100 |    96.77 |     100 |     100 | 69                
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   94.13 |    90.65 |   93.75 |   94.13 | ...89-290,425-426 
  ...background.ts |     100 |      100 |     100 |     100 |                   
  todoSnapshot.ts  |   89.33 |    93.47 |     100 |   89.33 | ...,66-78,180-181 
  updateCheck.ts   |     100 |    80.95 |     100 |     100 | 30-42             
  ...ow-keyword.ts |     100 |      100 |     100 |     100 |                   
 ...i/utils/export |   74.23 |    56.04 |   94.59 |   74.23 |                   
  collect.ts       |   71.21 |    63.06 |      96 |   71.21 | ...88-631,653-654 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   78.57 |    44.92 |     100 |   78.57 | ...40-345,357-359 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |     100 |      100 |     100 |     100 |                   
 ...ort/formatters |   52.92 |    42.42 |   71.42 |   52.92 |                   
  html.ts          |   84.61 |       50 |     100 |   84.61 | ...53,57-58,62-63 
  json.ts          |     100 |      100 |     100 |     100 |                   
  jsonl.ts         |   82.45 |    28.57 |     100 |   82.45 | ...48,50-51,65-66 
  markdown.ts      |   36.32 |       40 |      50 |   36.32 | ...16-219,233-295 
 src/ui/voice      |   81.12 |    73.04 |    79.1 |   81.12 |                   
  ...d-recorder.ts |     6.2 |      100 |       0 |     6.2 | ...33-159,162-163 
  ...o-recorder.ts |   84.61 |    93.33 |   57.14 |   84.61 | ...16-117,131-136 
  ...me-session.ts |   91.14 |     64.7 |   92.85 |   91.14 | ...76,282,292-295 
  sox-recorder.ts  |    92.7 |    71.87 |     100 |    92.7 | ...34-135,153-154 
  ...ailability.ts |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |     100 |      100 |     100 |     100 |                   
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  ...e-recorder.ts |   88.29 |    67.74 |   81.81 |   88.29 | ...,98-99,112,115 
  voice-refine.ts  |     100 |    93.33 |     100 |     100 | 92                
  ...ream-retry.ts |     100 |    88.88 |     100 |     100 | 18                
  ...am-session.ts |   87.45 |    63.33 |   81.81 |   87.45 | ...03,320-322,339 
  ...ranscriber.ts |     100 |      100 |     100 |     100 |                   
 src/utils         |   75.76 |    88.41 |   89.38 |   75.76 |                   
  acpModelUtils.ts |   95.49 |    94.23 |     100 |   95.49 | 44,68-69,73-74    
  apiPreconnect.ts |   96.72 |    97.05 |     100 |   96.72 | 165-168           
  ...ol-call-id.ts |    92.3 |    83.33 |     100 |    92.3 | 26-27             
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  cleanup.ts       |   82.53 |    93.33 |      80 |   82.53 | 74,105-115        
  commands.ts      |   96.96 |    97.95 |     100 |   96.96 | 123-125           
  commentJson.ts   |   90.51 |     92.1 |     100 |   90.51 | 67-76,116         
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  cpuProfiler.ts   |   70.38 |    71.83 |   88.88 |   70.38 | ...27,430-431,438 
  deepMerge.ts     |     100 |       90 |     100 |     100 | 41-43,49          
  ...ScopeUtils.ts |   97.56 |    88.88 |     100 |   97.56 | 67                
  doctorChecks.ts  |   70.31 |    74.57 |     100 |   70.31 | ...95-301,325-341 
  ...putCapture.ts |   90.65 |    86.17 |     100 |   90.65 | ...72,370,372-373 
  ...arResolver.ts |   97.14 |    96.55 |     100 |   97.14 | 125-126           
  errors.ts        |   90.85 |    96.36 |    92.3 |   90.85 | 69-70,298-310     
  events.ts        |     100 |      100 |     100 |     100 |                   
  ...on-mention.ts |   88.48 |     82.6 |     100 |   88.48 | ...56-160,164-168 
  gitUtils.ts      |    92.7 |    84.09 |     100 |    92.7 | ...07-110,158-161 
  ...AutoUpdate.ts |    92.2 |    94.87 |   88.88 |    92.2 | 130-141           
  ...tyWarnings.ts |     100 |      100 |     100 |     100 |                   
  ...lationInfo.ts |   97.71 |    94.18 |     100 |   97.71 | ...57,274-275,320 
  languageUtils.ts |   98.47 |    97.72 |     100 |   98.47 | 153-154           
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...er-mention.ts |     100 |    66.66 |     100 |     100 | 14,30,44-46       
  ...iagnostics.ts |   94.57 |    83.01 |   88.88 |   94.57 | ...05,311,315-317 
  ...serMessage.ts |     100 |      100 |     100 |     100 |                   
  ...onfigUtils.ts |   94.19 |    90.97 |     100 |   94.19 | ...28,434,437-441 
  ...iveHelpers.ts |   95.33 |    92.08 |     100 |   95.33 | ...51-452,550,563 
  osc.ts           |    97.5 |      100 |   88.88 |    97.5 | 195-196           
  package.ts       |   88.88 |    85.71 |     100 |   88.88 | 31-32             
  processUtils.ts  |     100 |      100 |     100 |     100 |                   
  readStdin.ts     |   93.67 |    94.11 |   85.71 |   93.67 | 79-83             
  relaunch.ts      |   93.22 |    81.25 |     100 |   93.22 | 65-67,80          
  resolvePath.ts   |     100 |      100 |     100 |     100 |                   
  runBudget.ts     |   99.35 |    96.77 |     100 |   99.35 | 119               
  sandbox-path.ts  |     100 |      100 |     100 |     100 |                   
  sandbox.ts       |   11.46 |    23.52 |   16.66 |   11.46 | ...5-763,770-1047 
  ...xImageName.ts |     100 |    77.77 |     100 |     100 | 10,18             
  sandboxMounts.ts |     100 |      100 |     100 |     100 |                   
  sessionPaths.ts  |   90.84 |    90.56 |     100 |   90.84 | ...81-182,185-186 
  settingsUtils.ts |   82.37 |    88.75 |      90 |   82.37 | ...20-738,745-753 
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...ate-verify.ts |     100 |      100 |     100 |     100 |                   
  ...one-update.ts |   27.55 |    76.11 |   45.83 |   27.55 | ...44-845,848-867 
  ...upProfiler.ts |   98.47 |    94.66 |     100 |   98.47 | 132-133,308       
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       60 |     100 |     100 | 23,32             
  systemInfo.ts    |   95.12 |    90.27 |     100 |   95.12 | ...54-255,260-264 
  ...InfoFields.ts |    87.5 |    65.85 |     100 |    87.5 | ...24-125,146-147 
  ...alSequence.ts |     100 |    97.61 |     100 |     100 | 60                
  ...iffPreview.ts |   94.11 |    83.33 |     100 |   94.11 | 13                
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...ansionHook.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   87.75 |       75 |     100 |   87.75 | 47-48,53-54,57-58 
  version.ts       |     100 |    66.66 |     100 |     100 | 11                
  ...ingHandler.ts |     100 |      100 |     100 |     100 |                   
  windowTitle.ts   |   95.45 |    93.33 |     100 |   95.45 | 54-55             
  ...WithBackup.ts |   65.04 |    77.77 |     100 |   65.04 | 97,112,133-172    
 ...s/housekeeping |   91.63 |    91.02 |      95 |   91.63 |                   
  cleanup.ts       |   95.77 |    95.83 |     100 |   95.77 | 70-72             
  ...eractionAt.ts |     100 |      100 |     100 |     100 |                   
  scheduler.ts     |   91.91 |    90.47 |    87.5 |   91.91 | 58-62,73,131-135  
  throttledOnce.ts |   86.66 |     86.2 |     100 |   86.66 | ...99,105,137-138 
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |    85.8 |    85.28 |   87.18 |    85.8 |                   
 src               |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/__mocks__/fs  |       0 |        0 |       0 |       0 |                   
  promises.ts      |       0 |        0 |       0 |       0 | 1-48              
 src/agents        |   90.37 |    83.44 |   95.36 |   90.37 |                   
  ...transcript.ts |    92.6 |     87.5 |     100 |    92.6 | ...46,365-366,497 
  ...ent-resume.ts |   83.52 |    72.79 |   79.41 |   83.52 | ...1284-1288,1291 
  ...ound-tasks.ts |   96.53 |     90.4 |     100 |   96.53 | ...1353,1373-1376 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ent-result.ts |    96.8 |    92.68 |     100 |    96.8 | 106,129-131       
  ...n-registry.ts |   95.65 |    89.28 |     100 |   95.65 | ...12-413,485-489 
  ...w-snapshot.ts |   91.86 |       75 |     100 |   91.86 | ...54,178,185-187 
 src/agents/arena  |   76.25 |     67.5 |   78.94 |   76.25 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   75.01 |    64.22 |   78.57 |   75.01 | ...1874,1880-1881 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    72.34 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |   77.89 |    84.85 |   76.28 |   77.89 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   90.17 |    84.12 |   93.33 |   90.17 | ...71,673,675-676 
  TmuxBackend.ts   |    90.7 |    76.55 |   97.36 |    90.7 | ...87,697,743-747 
  detect.ts        |   31.25 |      100 |       0 |   31.25 | 34-88             
  index.ts         |     100 |      100 |     100 |     100 |                   
  iterm-it2.ts     |     100 |     92.1 |     100 |     100 | 37-38,106         
  tmux-commands.ts |    6.64 |      100 |    3.03 |    6.64 | ...93-363,386-503 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...agents/runtime |   88.49 |    85.84 |   83.54 |   88.49 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   79.97 |    76.22 |      68 |   79.97 | ...2030,2057-2104 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   87.93 |    79.06 |   63.63 |   87.93 | ...00-401,404-405 
  ...nteractive.ts |   81.01 |    82.35 |   76.66 |   81.01 | ...33,535-538,541 
  ...statistics.ts |   98.19 |    82.35 |     100 |   98.19 | 127,151,192,225   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ool-policy.ts |   98.34 |      100 |    92.3 |   98.34 | 81-82             
  ...low-budget.ts |     100 |      100 |     100 |     100 |                   
  ...ow-journal.ts |   91.76 |    75.86 |     100 |   91.76 | ...38-139,179-181 
  ...chestrator.ts |   91.79 |    87.79 |   82.35 |   91.79 | ...1774,1823-1826 
  ...ow-prompts.ts |     100 |      100 |     100 |     100 |                   
  ...ow-sandbox.ts |   96.87 |    94.51 |     100 |   96.87 | ...24-325,330-331 
  ...flow-saved.ts |   96.51 |    94.36 |     100 |   96.51 | 134-135,234-237   
  ...flow-stall.ts |    97.9 |    83.33 |     100 |    97.9 | 138-139,236       
 src/agents/tasks  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/team   |   81.86 |    84.09 |    87.4 |   81.86 |                   
  TeamManager.ts   |    72.1 |     79.6 |   78.84 |    72.1 | ...1628,1651-1652 
  identity.ts      |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...sionBridge.ts |     100 |      100 |     100 |     100 |                   
  mailbox.ts       |   94.76 |    86.36 |   92.85 |   94.76 | 86-87,348-354     
  ...ptAddendum.ts |     100 |      100 |     100 |     100 |                   
  tasks.ts         |   88.85 |    82.56 |   96.29 |   88.85 | ...-990,1034-1035 
  team-events.ts   |   60.52 |      100 |      50 |   60.52 | ...37-141,148-152 
  teamHelpers.ts   |   92.02 |    94.91 |   95.23 |   92.02 | ...31-332,368-378 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...eam/test-utils |   94.39 |    94.26 |   98.21 |   94.39 |                   
  ...on-harness.ts |   96.49 |    84.21 |     100 |   96.49 | 128-129,141-142   
  fake-agent.ts    |   98.49 |    95.08 |     100 |   98.49 | 201-203           
  fake-backend.ts  |   86.46 |    97.61 |   95.83 |   86.46 | 124-146           
 src/config        |   82.69 |    86.08 |   70.94 |   82.69 |                   
  approval-mode.ts |     100 |      100 |     100 |     100 |                   
  ...xtDefaults.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |    81.6 |     85.6 |   68.26 |    81.6 | ...6431,6436-6437 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |   94.63 |    91.86 |   89.58 |   94.63 | ...15-416,419-420 
 ...nfirmation-bus |   98.29 |    97.14 |     100 |   98.29 |                   
  message-bus.ts   |   98.14 |    97.05 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/core          |   90.35 |    85.35 |   92.34 |   90.35 |                   
  baseLlmClient.ts |   87.19 |    79.68 |      80 |   87.19 | ...18,631,685-687 
  client.ts        |   89.83 |    83.16 |   90.76 |   89.83 | ...2912,2980-2981 
  ...tGenerator.ts |   88.07 |       75 |     100 |   88.07 | ...84-388,396-400 
  ...lScheduler.ts |   89.72 |     84.3 |   95.65 |   89.72 | ...4839,4867-4878 
  geminiChat.ts    |   89.97 |    86.46 |   95.12 |   89.97 | ...3651,3718-3719 
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  ...MediaLimit.ts |     100 |    95.83 |     100 |     100 | 96                
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | ...9,62-66,69-110 
  logger.ts        |   87.41 |    87.02 |     100 |   87.41 | ...64-568,614-628 
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   92.59 |       75 |      50 |   92.59 | 41-42             
  ...on-helpers.ts |   87.61 |    76.92 |     100 |   87.61 | ...00-201,215-224 
  ...issionFlow.ts |   98.79 |       96 |     100 |   98.79 | 97                
  prompts.ts       |   88.93 |    89.55 |   72.72 |   88.93 | ...-910,1113-1114 
  ...ing-effort.ts |     100 |      100 |     100 |     100 |                   
  ...t-profiler.ts |   96.82 |     80.3 |   88.23 |   96.82 | ...08,115-116,121 
  ...port-retry.ts |     100 |      100 |     100 |     100 |                   
  tokenLimits.ts   |     100 |    90.62 |     100 |     100 | 25,60-61          
  ...allIdUtils.ts |   98.23 |     92.1 |     100 |   98.23 | 36,45             
  ...okTriggers.ts |   99.45 |    92.43 |     100 |   99.45 | 182,193           
  ...terruption.ts |     100 |       92 |     100 |     100 | 86,104            
  turn.ts          |   98.49 |    91.17 |     100 |   98.49 | ...93,621-622,668 
 ...ntentGenerator |   95.43 |    83.26 |   94.64 |   95.43 |                   
  ...tGenerator.ts |   97.01 |       85 |   94.11 |   97.01 | ...1203,1231-1233 
  converter.ts     |   94.51 |    80.72 |     100 |   94.51 | ...06-607,617,823 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   89.93 |    71.83 |   93.33 |   89.93 |                   
  ...tGenerator.ts |    88.3 |    71.21 |   92.85 |    88.3 | ...19-325,343-344 
  index.ts         |     100 |       80 |     100 |     100 | 50                
 ...ntentGenerator |   94.61 |    84.69 |    92.1 |   94.61 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   94.51 |    83.39 |   91.66 |   94.51 | ...1113-1114,1142 
  ...tDetection.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   89.91 |    86.14 |   94.44 |   89.91 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   88.77 |    83.53 |   96.29 |   88.77 | ...1575,1744-1759 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   60.31 |       75 |      50 |   60.31 | ...71,74-78,90-94 
  ...tGenerator.ts |    66.4 |    70.58 |   88.88 |    66.4 | ...51-157,168-169 
  pipeline.ts      |   96.12 |     87.5 |     100 |   96.12 | ...47-848,856,924 
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   91.66 |    89.24 |     100 |   91.66 | ...89-390,427-428 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   96.47 |    89.51 |   96.49 |   96.47 |                   
  dashscope.ts     |   97.43 |    91.72 |   94.73 |   97.43 | ...70-371,513-514 
  deepseek.ts      |   94.91 |    89.36 |     100 |   94.91 | ...31-132,145-146 
  default.ts       |    97.5 |    96.55 |   88.88 |    97.5 | 122-123,193       
  index.ts         |     100 |      100 |     100 |     100 |                   
  mimo.ts          |   94.11 |    66.66 |     100 |   94.11 | 29,52-53          
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  mistral.ts       |   96.07 |    73.33 |     100 |   96.07 | 32-33             
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
  utils.ts         |     100 |      100 |     100 |     100 |                   
  zai.ts           |   92.13 |    82.14 |     100 |   92.13 | ...,39-40,135-137 
 src/extension     |   80.33 |    79.52 |   88.15 |   80.33 |                   
  ...-converter.ts |   78.29 |    70.87 |     100 |   78.29 | ...1108,1153-1154 
  corruptFile.ts   |     100 |       50 |     100 |     100 | 40-45             
  ...-converter.ts |    73.8 |       75 |     100 |    73.8 | 44-54             
  ...me-refresh.ts |     100 |      100 |     100 |     100 |                   
  ...ionManager.ts |   69.44 |    68.27 |   68.33 |   69.44 | ...1667,1689-1690 
  ...references.ts |     100 |     90.9 |     100 |     100 | ...05,129,197,200 
  ...onSettings.ts |   92.65 |    91.89 |     100 |   92.65 | ...28-232,312-313 
  ...-converter.ts |    75.9 |    83.33 |   85.71 |    75.9 | ...98,202,214-248 
  github.ts        |   84.71 |    85.24 |     100 |   84.71 | ...61-662,670-671 
  http-client.ts   |   84.61 |       80 |     100 |   84.61 | 20-21             
  i18n.ts          |   78.26 |    95.83 |      50 |   78.26 | 104-110,116-123   
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   87.11 |    84.28 |     100 |   87.11 | ...44,348-354,429 
  npm.ts           |   74.67 |    71.64 |     100 |   74.67 | ...19-421,428-432 
  override.ts      |   94.11 |    88.88 |     100 |   94.11 | 63-64,81-82       
  redaction.ts     |     100 |      100 |     100 |     100 |                   
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-107,141-146    
  ...ceRegistry.ts |   93.96 |    83.14 |     100 |   93.96 | ...35-341,362-363 
  storage.ts       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.75 |    83.33 |     100 |   88.75 | ...28-231,234-237 
 src/followup      |   77.83 |    78.48 |    90.9 |   77.83 |                   
  followupState.ts |   98.44 |    95.74 |     100 |   98.44 | 236-237           
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   96.29 |    88.88 |     100 |   96.29 | 78,108,122        
  speculation.ts   |   65.48 |    55.69 |   71.42 |   65.48 | ...75-576,583-584 
  ...onToolGate.ts |     100 |    96.55 |     100 |     100 | 96                
  ...nGenerator.ts |   72.03 |    81.15 |   83.33 |   72.03 | ...68-219,331-333 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/goals         |   89.57 |    83.57 |   94.44 |   89.57 |                   
  ...eGoalStore.ts |    85.1 |    95.45 |   84.61 |    85.1 | ...63-166,174-182 
  goalHook.ts      |   97.26 |    91.66 |     100 |   97.26 | 100-105           
  goalJudge.ts     |   84.33 |    74.28 |     100 |   84.33 | ...57-358,366-368 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/hooks         |    87.4 |     86.1 |   88.61 |    87.4 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...bortSignal.ts |     100 |      100 |     100 |     100 |                   
  context-usage.ts |     100 |      100 |     100 |     100 |                   
  ...terpolator.ts |   96.66 |    93.33 |     100 |   96.66 | 66-67             
  ...HookRunner.ts |   96.68 |    87.23 |     100 |   96.68 | 110-112,231-233   
  ...Aggregator.ts |   96.57 |    91.48 |     100 |   96.57 | ...20-321,402,404 
  ...entHandler.ts |   95.31 |    83.33 |   94.44 |   95.31 | ...25,982-983,993 
  hookPlanner.ts   |   87.44 |    85.18 |   86.66 |   87.44 | ...20-224,231-242 
  hookRegistry.ts  |   92.53 |    85.43 |     100 |   92.53 | ...39,458,462,466 
  hookRunner.ts    |   62.42 |    72.04 |   66.66 |   62.42 | ...64-765,774-775 
  hookSystem.ts    |   87.16 |      100 |   69.56 |   87.16 | ...26-727,733-734 
  ...HookRunner.ts |   75.51 |     61.9 |      80 |   75.51 | ...05-406,424-425 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...edCallback.ts |     100 |      100 |     100 |     100 |                   
  ...HookRunner.ts |   96.37 |     90.9 |      90 |   96.37 | 342-350,424-425   
  ...SkillHooks.ts |   78.75 |       75 |   66.66 |   78.75 | 62-66,137-152     
  ...oksManager.ts |   94.87 |    88.88 |     100 |   94.87 | ...84,325,327-329 
  ssrfGuard.ts     |   77.22 |    86.74 |     100 |   77.22 | ...57,261-267,273 
  stopHookCap.ts   |     100 |      100 |     100 |     100 |                   
  trustedHooks.ts  |      90 |    52.63 |     100 |      90 | ...53,66-67,97-98 
  types.ts         |   94.22 |    96.09 |   88.88 |   94.22 | ...40-541,626-630 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
 src/ide           |   76.87 |    84.98 |   79.03 |   76.87 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |   68.92 |    84.57 |   68.29 |   68.92 | ...1057,1086-1094 
  ide-installer.ts |   89.06 |    79.31 |     100 |   89.06 | ...36,143-147,160 
  ideContext.ts    |     100 |      100 |     100 |     100 |                   
  process-utils.ts |   84.84 |    71.79 |     100 |   84.84 | ...37,151,193-194 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/lsp           |   58.96 |    70.57 |   66.14 |   58.96 |                   
  ...nfigLoader.ts |   80.55 |       72 |   95.45 |   80.55 | ...02-504,508-514 
  ...ionFactory.ts |   42.81 |    73.07 |      50 |   42.81 | ...76-427,433-450 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   75.73 |     80.1 |   79.66 |   75.73 | ...1346,1352-1382 
  ...eLspClient.ts |   32.78 |       80 |   16.66 |   32.78 | ...89-293,299-300 
  ...LspService.ts |      60 |    73.36 |   78.26 |      60 | ...1575,1635-1645 
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |   82.39 |    77.73 |   78.33 |   82.39 |                   
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   79.52 |    58.06 |     100 |   79.52 | ...33-940,947-949 
  ...en-storage.ts |   98.78 |    97.95 |     100 |   98.78 | 106-107           
  oauth-utils.ts   |   73.61 |    85.24 |    92.3 |   73.61 | ...46-366,392-421 
  ...n-provider.ts |   89.83 |       96 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   82.12 |    88.19 |   89.28 |   82.12 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   87.08 |    87.27 |   95.23 |   87.08 | ...00-201,214-215 
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   68.14 |    82.35 |   64.28 |   68.14 | ...81-295,298-314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/memory        |   85.59 |    80.73 |      88 |   85.59 |                   
  ...nel-memory.ts |    88.7 |    80.95 |      90 |    88.7 | ...00-204,209-212 
  const.ts         |   94.28 |     92.3 |     100 |   94.28 | 66-67             
  dream.ts         |    64.6 |    72.22 |      50 |    64.6 | ...04-109,124-165 
  ...entPlanner.ts |     100 |    81.81 |     100 |     100 | 126,136           
  entries.ts       |   75.59 |    84.84 |   83.33 |   75.59 | ...56-157,172-180 
  extract.ts       |   91.48 |    75.75 |     100 |   91.48 | ...99,118-121,189 
  ...entPlanner.ts |   91.51 |    76.19 |     100 |   91.51 | ...04,113-116,290 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |   81.83 |       75 |   83.33 |   81.83 | ...51,474,478-507 
  indexer.ts       |   94.14 |       84 |     100 |   94.14 | ...32-233,334,337 
  manager.ts       |   78.44 |    82.29 |   77.77 |   78.44 | ...1482,1495-1497 
  ...ent-config.ts |   81.98 |    79.72 |   82.35 |   81.98 | ...25,244,251-257 
  memoryAge.ts     |   90.47 |       80 |     100 |   90.47 | 50-51             
  paths.ts         |   93.17 |    93.93 |     100 |   93.17 | ...02-303,311-312 
  ...ing-skills.ts |     100 |       72 |     100 |     100 | 31-35,73-78,97    
  prompt.ts        |   96.96 |     86.2 |     100 |   96.96 | ...22,225,560-561 
  recall.ts        |   82.06 |       75 |    90.9 |   82.06 | ...59-364,395-406 
  ...ceSelector.ts |    93.1 |    81.81 |     100 |    93.1 | ...25,127-128,136 
  remember.ts      |   98.89 |    89.79 |     100 |   98.89 | 50,70             
  scan.ts          |   93.12 |    77.41 |     100 |   93.12 | ...08-109,154,157 
  ...et-scanner.ts |     100 |      100 |     100 |     100 |                   
  ...entPlanner.ts |   71.68 |    65.51 |   68.75 |   71.68 | ...90-394,397,403 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   93.33 |    81.25 |     100 |   93.33 | ...,94-95,119-120 
  ...git-status.ts |     100 |     87.5 |     100 |     100 | 30                
  ...cret-guard.ts |     100 |      100 |     100 |     100 |                   
  ...emory-sync.ts |   94.24 |    82.85 |     100 |   94.24 | ...34-236,246-247 
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ontextFile.ts |   79.38 |    78.33 |   81.81 |   79.38 | ...58-272,286-291 
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   91.25 |    88.35 |   89.74 |   91.25 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   90.55 |    91.42 |     100 |   90.55 | 146,152,155-164   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   74.22 |       44 |   84.61 |   74.22 | ...,67-74,106-117 
  ...igResolver.ts |   98.71 |    93.33 |     100 |   98.71 | 166,328,334       
  modelRegistry.ts |     100 |    98.91 |     100 |     100 | 177               
  modelsConfig.ts  |   88.07 |    86.78 |   85.36 |   88.07 | ...1358,1387-1388 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/output        |     100 |      100 |     100 |     100 |                   
  ...-formatter.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/permissions   |   83.02 |    91.25 |   69.95 |   83.02 |                   
  autoMode.ts      |   97.78 |    94.08 |     100 |   97.78 | ...42,570-577,686 
  ...transcript.ts |      98 |       84 |     100 |      98 | 200-201           
  classifier.ts    |      94 |    94.44 |     100 |      94 | 158-165,385-389   
  ...erousRules.ts |     100 |    89.36 |     100 |     100 | 110,133,147,175   
  ...alTracking.ts |     100 |      100 |     100 |     100 |                   
  ...e-commands.ts |   86.77 |     73.8 |     100 |   86.77 | 131-141,210-214   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   85.11 |    89.24 |      80 |   85.11 | ...1034,1140-1144 
  rule-parser.ts   |   94.36 |    92.63 |     100 |   94.36 | ...1211,1245-1247 
  ...-semantics.ts |   70.36 |    91.07 |   46.66 |   70.36 | ...2237,2300-2303 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...sifier-prompts |   99.04 |    95.23 |     100 |   99.04 |                   
  system-prompt.ts |   99.04 |    95.23 |     100 |   99.04 | 220               
 src/plan-gate     |   76.16 |    91.42 |      80 |   76.16 |                   
  ...viewAgents.ts |   52.28 |    88.46 |   66.66 |   52.28 | ...24-220,242-243 
  ...provalGate.ts |   92.47 |    92.85 |   85.71 |   92.47 | ...86-187,268-274 
  state.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/providers     |   79.38 |       75 |   68.75 |   79.38 |                   
  all-providers.ts |   69.23 |      100 |       0 |   69.23 | 71-72,76-82,86-92 
  index.ts         |     100 |      100 |     100 |     100 |                   
  install.ts       |   93.11 |     84.5 |     100 |   93.11 | ...56-257,330-331 
  ...der-config.ts |    72.6 |    69.49 |   73.91 |    72.6 | ...94-495,502-511 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...viders/presets |   97.56 |    89.28 |   55.55 |   97.56 |                   
  ...oding-plan.ts |   87.34 |      100 |       0 |   87.34 | 82-84,87-89,91-94 
  ...a-standard.ts |     100 |      100 |     100 |     100 |                   
  ...token-plan.ts |     100 |      100 |     100 |     100 |                   
  ...m-provider.ts |   97.05 |    81.25 |      75 |   97.05 | 118-119           
  deepseek.ts      |     100 |      100 |     100 |     100 |                   
  idealab.ts       |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  requesty.ts      |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/qwen          |    85.3 |     78.8 |   95.89 |    85.3 |                   
  ...tGenerator.ts |   98.64 |    98.18 |     100 |   98.64 | 105-106           
  qwenOAuth2.ts    |   82.55 |    73.71 |   90.62 |   82.55 | ...1183-1199,1229 
  ...kenManager.ts |   85.36 |    76.61 |     100 |   85.36 | ...52-757,778-783 
 src/resources     |     100 |      100 |     100 |     100 |                   
  ...e-registry.ts |     100 |      100 |     100 |     100 |                   
 src/services      |   89.93 |    85.69 |   95.99 |   89.93 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   97.35 |    85.34 |     100 |   97.35 | ...94,117,417-418 
  ...ionService.ts |   96.65 |    95.57 |     100 |   96.65 | ...76,692,821-829 
  ...ingService.ts |   87.19 |    82.68 |   83.72 |   87.19 | ...1462,1477-1478 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |   99.54 |    96.47 |     100 |   99.54 | 98                
  cronScheduler.ts |   96.11 |    91.32 |     100 |   96.11 | ...1178,1577-1578 
  cronTasksFile.ts |   95.52 |    90.47 |     100 |   95.52 | ...51,276-277,391 
  cronTasksLock.ts |   94.44 |    89.47 |     100 |   94.44 | ...02-103,132-133 
  ...eryService.ts |   96.22 |    93.75 |      90 |   96.22 | 121,155-156,161   
  ...oryService.ts |   88.17 |    79.02 |    92.3 |   88.17 | ...1303,1344-1347 
  fileReadCache.ts |     100 |      100 |     100 |     100 |                   
  ...temService.ts |   92.07 |    85.93 |    90.9 |   92.07 | ...09,211,323-330 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  ...reeService.ts |   74.05 |       69 |   95.74 |   74.05 | ...2170,2198-2199 
  ...references.ts |   98.39 |    88.88 |     100 |   98.39 | 154-155,215-216   
  ...ionService.ts |   98.09 |    97.15 |     100 |   98.09 | ...00-601,648-649 
  ...ticsDumper.ts |   98.37 |    95.23 |     100 |   98.37 | 185-186           
  ...ureMonitor.ts |   96.06 |    91.48 |   96.96 |   96.06 | ...49,850,864-866 
  ...orRegistry.ts |   97.27 |    91.22 |     100 |   97.27 | ...50-451,606-607 
  ...ttachments.ts |   97.24 |    90.34 |     100 |   97.24 | ...08,646,661-662 
  ...on-service.ts |    94.3 |    92.54 |   97.05 |    94.3 | ...79-581,633-641 
  sessionRecap.ts  |   67.56 |    43.47 |     100 |   67.56 | ...60,178,180-183 
  ...ionService.ts |   86.49 |    80.32 |   94.73 |   86.49 | ...1989,2059-2079 
  sessionTitle.ts  |   94.19 |    73.21 |     100 |   94.19 | ...43-246,277-278 
  ...ionService.ts |   84.03 |    78.09 |   96.96 |   84.03 | ...2406,2412-2417 
  ...pInhibitor.ts |   97.42 |    92.77 |     100 |   97.42 | ...30,169,369-370 
  ...Estimation.ts |     100 |    86.66 |     100 |     100 | 96-97             
  ...ageService.ts |   97.76 |    91.59 |   93.75 |   97.76 | ...61-262,366,567 
  ...UseSummary.ts |   94.63 |    88.46 |     100 |   94.63 | ...62-164,214-215 
  ...rd-service.ts |     100 |    88.37 |     100 |     100 | ...29,145-146,241 
  ...oryService.ts |   92.07 |    84.69 |     100 |   92.07 | ...47-450,502-503 
  ...reeCleanup.ts |   14.56 |      100 |   33.33 |   14.56 | 58-185            
  ...ionService.ts |   87.98 |    86.84 |     100 |   87.98 | ...38-439,455-456 
 ...icrocompaction |   99.35 |    95.69 |     100 |   99.35 |                   
  microcompact.ts  |   99.35 |    95.69 |     100 |   99.35 | 224-225,618       
 ...s/visionBridge |   96.64 |    94.17 |   94.11 |   96.64 |                   
  ...capability.ts |     100 |      100 |     100 |     100 |                   
  ...part-utils.ts |     100 |      100 |     100 |     100 |                   
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...ge-service.ts |   95.49 |     91.3 |      90 |   95.49 | ...12,430,443-444 
 src/skills        |   88.22 |    87.05 |   90.16 |   88.22 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |    93.33 |     100 |     100 | 93,112            
  skill-load.ts    |   94.84 |     87.5 |     100 |   94.84 | ...03,223,235-237 
  skill-manager.ts |   83.44 |    82.16 |   82.35 |   83.44 | ...1202,1209-1213 
  skill-paths.ts   |   89.65 |    86.95 |     100 |   89.65 | ...11-112,117-118 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |   97.91 |       98 |     100 |   97.91 | 277-278           
 ...ataviz/scripts |   80.06 |    95.23 |   88.23 |   80.06 |                   
  ...te_palette.js |   80.06 |    95.23 |   88.23 |   80.06 | 261-296,306-328   
 ...s/bundled/loop |   97.48 |    95.77 |     100 |   97.48 |                   
  ...omous-loop.ts |     100 |      100 |     100 |     100 |                   
  ...-task-file.ts |   94.85 |     92.4 |     100 |   94.85 | ...56,367,375-376 
  ...k-resolver.ts |     100 |      100 |     100 |     100 |                   
 src/subagents     |      86 |     85.9 |   94.33 |      86 |                   
  ...ter-schema.ts |     100 |    98.07 |     100 |     100 | 99                
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   81.51 |    80.66 |   91.17 |   81.51 | ...1450,1527-1528 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 47-52,63-68,71-76 
 src/telemetry     |   79.47 |    87.11 |   81.65 |   79.47 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...on-metrics.ts |   99.06 |    80.48 |     100 |   99.06 | 183,197           
  ...on-tracing.ts |   74.55 |    73.21 |   70.58 |   74.55 | ...95,350-352,368 
  ...attributes.ts |   97.47 |    93.15 |     100 |   97.47 | 39-44             
  ...ag-metrics.ts |     100 |    77.77 |     100 |     100 | 21,40             
  ...t-loop-lag.ts |     100 |    90.47 |     100 |     100 | 49,76             
  ...-exporters.ts |   65.78 |    83.33 |   55.55 |   65.78 | ...04-105,108-109 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   99.09 |    95.61 |      95 |   99.09 | 141,365-366       
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |   53.31 |    68.47 |      62 |   53.31 | ...1318,1335-1355 
  metrics.ts       |   76.07 |    78.57 |   78.94 |   76.07 | ...1021,1024-1035 
  ...attributes.ts |     100 |      100 |     100 |     100 |                   
  ...ime-config.ts |       0 |        0 |       0 |       0 | 1                 
  sanitize.ts      |      80 |    83.33 |     100 |      80 | 35-36,41-42       
  sdk.ts           |   86.75 |     88.4 |   66.66 |   86.75 | ...17-621,659-681 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |   90.06 |    88.25 |   96.55 |   90.06 | ...1541,1572-1575 
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  trace-context.ts |     100 |      100 |     100 |     100 |                   
  ...e-id-utils.ts |     100 |      100 |     100 |     100 |                   
  tracer.ts        |   98.56 |    88.63 |     100 |   98.56 | 52,101            
  types.ts         |   78.83 |    85.27 |   83.54 |   78.83 | ...1301,1305-1312 
  uiTelemetry.ts   |   93.07 |    92.85 |   83.33 |   93.07 | ...62,290,410-411 
 ...ry/qwen-logger |   71.09 |    83.17 |   67.24 |   71.09 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   71.09 |    83.01 |   66.66 |   71.09 | ...1079,1117-1118 
 src/test-utils    |   93.85 |    98.14 |   77.77 |   93.85 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |    92.3 |      100 |   74.19 |    92.3 | ...54,218-219,232 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   84.02 |    83.43 |   87.63 |   84.02 |                   
  ...erQuestion.ts |   90.74 |    82.43 |    92.3 |   90.74 | ...23-424,431-432 
  ...-registrar.ts |    77.7 |    66.66 |   66.66 |    77.7 | ...72-277,292-294 
  cron-create.ts   |   90.64 |    92.85 |   72.72 |   90.64 | ...,73-74,223-231 
  cron-delete.ts   |   97.56 |      100 |   83.33 |   97.56 | 31-32             
  cron-list.ts     |   98.23 |    95.23 |    87.5 |   98.23 | 56-57             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  edit.ts          |    82.7 |    86.77 |   81.25 |    82.7 | ...43-744,863-913 
  ...r-worktree.ts |   83.14 |    67.56 |    87.5 |   83.14 | ...84-187,278-279 
  enterPlanMode.ts |   82.35 |    73.68 |   85.71 |   82.35 | ...,86-91,124-138 
  exit-worktree.ts |   83.29 |    83.65 |   94.44 |   83.29 | ...14-515,537-538 
  exitPlanMode.ts  |    81.7 |    73.41 |     100 |    81.7 | ...28-531,561-564 
  glob.ts          |   94.61 |     87.5 |   92.85 |   94.61 | ...83,216,347,350 
  grep.ts          |   83.09 |    86.66 |   80.95 |   83.09 | ...60-661,711-712 
  ...adTracking.ts |     100 |      100 |     100 |     100 |                   
  loop-wakeup.ts   |   99.24 |    92.85 |     100 |   99.24 | 44                
  ls.ts            |   96.74 |    90.27 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.71 |     59.5 |   90.32 |   72.71 | ...1212,1214-1215 
  ...nt-manager.ts |   81.32 |    79.44 |    85.1 |   81.32 | ...3160,3162-3163 
  mcp-client.ts    |   75.61 |     83.6 |   86.04 |   75.61 | ...1926,1930-1933 
  ...ry-timeout.ts |     100 |      100 |     100 |     100 |                   
  mcp-errors.ts    |     100 |      100 |     100 |     100 |                   
  ...pool-entry.ts |   77.56 |    84.11 |   77.14 |   77.56 | ...1291,1299-1300 
  ...ool-events.ts |       8 |      100 |       0 |       8 | 132-158           
  mcp-pool-key.ts  |   97.46 |    93.93 |     100 |   97.46 | 175-176           
  ...ce-content.ts |   96.55 |    91.17 |     100 |   96.55 | 80-82             
  mcp-retry.ts     |   97.67 |    95.65 |     100 |   97.67 | 131-132           
  mcp-status.ts    |     100 |      100 |     100 |     100 |                   
  mcp-tool.ts      |   91.66 |    89.74 |   96.77 |   91.66 | ...20-721,771-772 
  ...sport-pool.ts |   83.49 |    80.15 |   84.61 |   83.49 | ...1409,1416-1420 
  ...ace-budget.ts |   87.27 |     82.6 |     100 |   87.27 | ...00-305,340-345 
  memory-config.ts |     100 |      100 |     100 |     100 |                   
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 101,108           
  monitor.ts       |   91.72 |    84.28 |   88.46 |   91.72 | ...92,605,803-808 
  notebook-edit.ts |   85.55 |    77.39 |   81.25 |   85.55 | ...86-902,948-949 
  ...escendants.ts |   36.17 |    64.51 |   55.55 |   36.17 | ...46-310,385-390 
  ...nforcement.ts |   82.57 |    89.74 |     100 |   82.57 | 174-185,234-247   
  read-file.ts     |   94.86 |    90.62 |   81.81 |   94.86 | ...06,309,392-393 
  ...p-resource.ts |   96.85 |      100 |   91.66 |   96.85 | 92-96             
  ...d-artifact.ts |    90.9 |    86.71 |    87.5 |    90.9 | ...13-414,428-440 
  ripGrep.ts       |   95.87 |     88.4 |   94.73 |   95.87 | ...56-657,663-664 
  ...-transport.ts |   71.42 |    55.55 |   71.42 |   71.42 | ...36-137,143-144 
  send-message.ts  |    82.3 |    89.65 |    62.5 |    82.3 | ...37-243,326-334 
  ...n-mcp-view.ts |   93.57 |     92.3 |      90 |   93.57 | 122-130           
  shell.ts         |   77.38 |    81.87 |   91.48 |   77.38 | ...4815,4878-4879 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   91.06 |    93.33 |   89.47 |   91.06 | ...71,475,520-542 
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-create.ts   |    94.4 |    93.33 |   81.81 |    94.4 | 45-49,63-64,95    
  task-list.ts     |   73.38 |    77.77 |   83.33 |   73.38 | ...02,105,109-116 
  task-stop.ts     |   93.14 |    96.15 |   85.71 |   93.14 | 39-40,54-64       
  task-update.ts   |   82.89 |    83.92 |    92.3 |   82.89 | ...14-422,454-465 
  team-create.ts   |   97.22 |    85.71 |   83.33 |   97.22 | 48-49,129-130     
  team-delete.ts   |   86.74 |    83.33 |   83.33 |   86.74 | 37-38,42-48,72-73 
  ...n-approval.ts |   92.14 |    96.77 |   77.77 |   92.14 | 38-39,42-43,93-99 
  todoWrite.ts     |   89.27 |    82.05 |   92.85 |   89.27 | ...50-555,577-578 
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   76.78 |    74.59 |   82.22 |   76.78 | ...96-897,905-906 
  tool-search.ts   |   96.19 |    89.72 |   93.33 |   96.19 | ...09,259-264,426 
  tools.ts         |   93.97 |     90.9 |   95.23 |   93.97 | ...36-537,553-559 
  web-fetch.ts     |   90.12 |    85.71 |   92.85 |   90.12 | ...11-312,326-327 
  write-file.ts    |   84.28 |    82.79 |   84.61 |   84.28 | ...87-690,727-762 
 src/tools/agent   |   84.44 |    84.79 |   86.31 |   84.44 |                   
  agent.ts         |   84.49 |    84.88 |    86.2 |   84.49 | ...3695,3717-3727 
  fork-subagent.ts |   83.14 |       80 |    87.5 |   83.14 | 83-101,133-134    
 ...tools/artifact |   95.78 |    92.51 |   88.63 |   95.78 |                   
  artifact-tool.ts |   91.46 |    88.46 |   71.42 |   91.46 | ...13-314,322-325 
  ...-publisher.ts |     100 |    85.71 |     100 |     100 | 32                
  ...-publisher.ts |   96.74 |    97.72 |    87.5 |   96.74 | 29-30,156-157     
  html.ts          |     100 |    96.77 |     100 |     100 | 122               
  ...-publisher.ts |     100 |       80 |     100 |     100 | 30                
  oss-publisher.ts |    98.1 |    91.48 |     100 |    98.1 | 43-45             
  publisher.ts     |     100 |      100 |     100 |     100 |                   
 ...s/computer-use |   90.21 |    82.17 |   78.08 |   90.21 |                   
  bootstrap.ts     |   59.42 |    80.95 |   41.66 |   59.42 | ...35-339,341-345 
  client.ts        |   80.11 |       90 |   77.77 |   80.11 | ...97,242-243,274 
  constants.ts     |     100 |    94.73 |     100 |     100 | 129,256           
  downloader.ts    |   65.29 |    52.77 |   58.33 |   65.29 | ...99-300,316-355 
  index.ts         |     100 |      100 |     100 |     100 |                   
  install-state.ts |   94.44 |    72.72 |     100 |   94.44 | 44-45             
  ...n-detector.ts |     100 |     87.5 |     100 |     100 | 50                
  schemas.ts       |     100 |      100 |     100 |     100 |                   
  tool.ts          |    96.3 |    85.71 |     100 |    96.3 | 75-76,184,252-258 
 ...tools/workflow |   87.46 |    79.41 |   85.71 |   87.46 |                   
  workflow.ts      |   87.46 |    79.41 |   85.71 |   87.46 | ...51-652,664-667 
 src/utils         |   91.24 |    89.11 |   95.65 |   91.24 |                   
  LruCache.ts      |       0 |        0 |       0 |       0 | 1-41              
  ...Controller.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |   94.76 |    93.26 |     100 |   94.76 | ...30-531,634-638 
  bareMode.ts      |   81.81 |      100 |      50 |   81.81 | 18-19             
  browser.ts       |   86.84 |    78.94 |     100 |   86.84 | 34,36-37,65-66    
  btwUtils.ts      |   13.95 |      100 |       0 |   13.95 | 17-31,34-55       
  bundlePaths.ts   |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...ncyLimiter.ts |   94.64 |    95.23 |     100 |   94.64 | 64-66             
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |   91.11 |    89.47 |     100 |   91.11 | ...46-147,154-155 
  ...tion-chain.ts |     100 |    96.15 |     100 |     100 | 91                
  cronDisplay.ts   |     100 |    91.66 |     100 |     100 | 15,43,57          
  cronParser.ts    |   95.34 |     93.1 |     100 |   95.34 | 41-42,47-48,70-71 
  debugLogger.ts   |   96.55 |    94.54 |   88.23 |   96.55 | 190-194           
  editHelper.ts    |   93.63 |     83.9 |     100 |   93.63 | ...28-429,463-464 
  editor.ts        |   97.65 |    95.45 |     100 |   97.65 | ...35-336,338-339 
  env.ts           |     100 |      100 |     100 |     100 |                   
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  ...entContext.ts |   96.55 |       88 |   96.77 |   96.55 | ...74,476-477,544 
  errorParsing.ts  |    97.7 |    97.05 |     100 |    97.7 | 72-73             
  ...rReporting.ts |   95.65 |    93.33 |     100 |   95.65 | 37-38             
  errors.ts        |   81.73 |    90.55 |   57.89 |   81.73 | ...08-324,328-334 
  fetch.ts         |   72.45 |    81.81 |   71.42 |   72.45 | ...31,142-143,162 
  fileUtils.ts     |   94.44 |    90.65 |   96.15 |   94.44 | ...1596,1621-1622 
  forkedAgent.ts   |   92.45 |    82.35 |   93.75 |   92.45 | ...34,642,647-654 
  formatters.ts    |   81.81 |       75 |     100 |   81.81 | 15-16             
  ...eUtilities.ts |   89.21 |    86.66 |     100 |   89.21 | 16-17,49-55,65-66 
  ...rStructure.ts |   94.36 |    94.28 |     100 |   94.36 | ...17-120,330-335 
  getPty.ts        |   31.57 |       50 |     100 |   31.57 | 26-38             
  gitDiff.ts       |   92.36 |    80.09 |     100 |   92.36 | ...55-856,928-929 
  gitDirect.ts     |   98.46 |    90.17 |     100 |   98.46 | 148,268,352       
  ...noreParser.ts |   94.59 |    92.59 |     100 |   94.59 | ...05-106,140-141 
  gitUtils.ts      |   72.91 |    90.32 |   83.33 |   72.91 | ...,77-78,102-153 
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 27                
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   94.42 |    94.04 |     100 |   94.42 | ...16-317,356-359 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...iagnostics.ts |    96.4 |     94.2 |     100 |    96.4 | ...66,293-294,376 
  ...yDiscovery.ts |    92.4 |    89.01 |     100 |    92.4 | ...28,331,522-525 
  ...tProcessor.ts |   93.77 |    89.02 |     100 |   93.77 | ...13-319,406-407 
  ...Inspectors.ts |     100 |      100 |     100 |     100 |                   
  modelId.ts       |   98.96 |    98.14 |     100 |   98.96 | 153               
  ...kerChecker.ts |    90.9 |    91.66 |     100 |    90.9 | 73-79             
  notebook.ts      |   94.57 |    89.91 |   95.83 |   94.57 | ...21,333,385-387 
  openaiLogger.ts  |   91.66 |    89.74 |     100 |   91.66 | ...26-228,251-256 
  partUtils.ts     |     100 |    98.61 |     100 |     100 | 206               
  pathReader.ts    |   97.77 |       90 |     100 |   97.77 | 93,121            
  paths.ts         |   93.72 |    92.59 |     100 |   93.72 | ...47-448,450-452 
  pdf.ts           |   92.59 |    86.76 |     100 |   92.59 | ...54-555,596-601 
  projectPath.ts   |     100 |      100 |     100 |     100 |                   
  projectRoot.ts   |   71.73 |    78.57 |     100 |   71.73 | 54-66             
  ...ectSummary.ts |   89.62 |    72.41 |     100 |   89.62 | ...40-145,196-199 
  ...tIdContext.ts |     100 |      100 |     100 |     100 |                   
  proxyUtils.ts    |     100 |      100 |     100 |     100 |                   
  ...rDetection.ts |   59.15 |    76.92 |     100 |   59.15 | ...5,89-90,96-101 
  ...noreParser.ts |   92.63 |    91.37 |     100 |   92.63 | ...72-173,192-193 
  rateLimit.ts     |   93.75 |    89.42 |     100 |   93.75 | ...13,218-219,262 
  ...text-range.ts |   97.73 |    93.24 |     100 |   97.73 | 85-86,107,262-263 
  readManyFiles.ts |   96.29 |     87.5 |     100 |   96.29 | 225,275,286-290   
  retry.ts         |   95.93 |    91.83 |     100 |   95.93 | ...33,524-525,543 
  retryContext.ts  |     100 |      100 |     100 |     100 |                   
  ...sification.ts |   97.65 |    96.96 |     100 |   97.65 | ...00,250-251,277 
  retryPolicy.ts   |   97.72 |    90.56 |     100 |   97.72 | 130-131           
  ripgrepUtils.ts  |   50.94 |    85.71 |      70 |   50.94 | ...54-255,268-346 
  ...sDiscovery.ts |   97.46 |    93.05 |     100 |   97.46 | ...04,182-183,202 
  ...iagnostics.ts |   83.08 |     67.5 |   92.59 |   83.08 | ...23,543-544,550 
  ...tchOptions.ts |      83 |    86.77 |   95.45 |      83 | ...68,593,622-631 
  ...odelPrefix.ts |     100 |      100 |     100 |     100 |                   
  runtimeStatus.ts |    97.5 |    89.74 |     100 |    97.5 | 162-163           
  safe-mode.ts     |     100 |      100 |     100 |     100 |                   
  safeJsonParse.ts |   74.07 |    83.33 |     100 |   74.07 | 40-46             
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   91.13 |    89.47 |     100 |   91.13 | ...41-42,96,98-99 
  ...aValidator.ts |   92.09 |    83.65 |   90.47 |   92.09 | ...60,882-883,896 
  ...r-launcher.ts |   96.35 |    93.97 |   85.71 |   96.35 | ...35-336,347-348 
  sedEditParser.ts |   91.72 |    92.12 |     100 |   91.72 | ...36-539,615-616 
  ...nIdContext.ts |     100 |      100 |     100 |     100 |                   
  ...orageUtils.ts |   95.98 |     83.8 |     100 |   95.98 | ...70,386,466,485 
  ...-pager-env.ts |     100 |      100 |     100 |     100 |                   
  shell-utils.ts   |   85.76 |     88.2 |     100 |   85.76 | ...2179,2186-2190 
  ...lAstParser.ts |   95.57 |    85.88 |     100 |   95.57 | ...1066-1068,1078 
  ...ContextEnv.ts |     100 |      100 |     100 |     100 |                   
  ...nlyChecker.ts |   95.08 |    91.66 |     100 |   95.08 | ...15-316,324-325 
  sideQuery.ts     |   86.82 |     86.2 |     100 |   86.82 | ...81-187,189-195 
  ...pEventSink.ts |     100 |       80 |     100 |     100 | 61                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   77.77 |       50 |     100 |   77.77 | 44,54-59          
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  terminalSafe.ts  |     100 |      100 |     100 |     100 |                   
  ...Serializer.ts |   98.72 |       90 |     100 |   98.72 | 42-43,134,201-203 
  testUtils.ts     |   53.33 |      100 |   33.33 |   53.33 | ...53,59-64,70-72 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  textUtils.ts     |      65 |      100 |      75 |      65 | 56-75             
  thoughtUtils.ts  |     100 |    95.65 |     100 |     100 | 99                
  ...-converter.ts |   95.23 |    85.71 |     100 |   95.23 | 36-37             
  tool-utils.ts    |    95.2 |    93.61 |     100 |    95.2 | ...58-159,162-163 
  ...ultCleanup.ts |   54.62 |       64 |      75 |   54.62 | ...03-105,108-134 
  ...Compaction.ts |   95.68 |    95.32 |     100 |   95.68 | ...29-334,533-534 
  truncation.ts    |   75.55 |    85.86 |   71.42 |   75.55 | ...44-449,453-477 
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...aceContext.ts |   95.81 |    89.39 |     100 |   95.81 | ...74-275,299-301 
  xml.ts           |    97.8 |    87.69 |     100 |    97.8 | 98-99             
  yaml-parser.ts   |   83.87 |    77.27 |     100 |   83.87 | ...31-234,239-240 
 ...ils/filesearch |   83.68 |    80.38 |   94.69 |   83.68 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |   82.47 |    76.22 |      95 |   82.47 | ...1525,1559-1560 
  fileSearch.ts    |   93.78 |    87.67 |     100 |   93.78 | ...71-272,274-275 
  fzfWorker.ts     |       0 |        0 |       0 |       0 | 1-109             
  ...rkerHandle.ts |   84.05 |    75.43 |   89.47 |   84.05 | ...30-334,340-341 
  ignore.ts        |     100 |    97.36 |     100 |     100 | 187               
  result-cache.ts  |     100 |    93.75 |     100 |     100 | 49                
 ...uest-tokenizer |   69.01 |     74.5 |   84.37 |   69.01 |                   
  ...eTokenizer.ts |   65.72 |    74.02 |    92.3 |   65.72 | ...65-466,479-533 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tTokenizer.ts |   68.39 |    69.49 |    90.9 |   68.39 | ...24-325,327-328 
  ...ageFormats.ts |   76.92 |      100 |   33.33 |   76.92 | 46-49,56-57       
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
-------------------|---------|----------|---------|---------|-------------------

For detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Suggestions — commit 3a9e75b21

File Issue Suggested fix
MessageList.tsx:1829 Unused panel variable in clamping useLayoutEffect — declared and null-checked but never referenced after the guard Remove the variable; check panelRef.current directly in the guard
MessageList.tsx:1772-1777 buildTooltip can return null when panelRef.current is null, but syncTooltip and showTooltip pass the result to setTooltip without null guard, silently clearing tooltip state Add ?? current fallback in syncTooltip and null guard in showTooltip
MessageList.tsx:1750 findTooltipAnchor uses querySelectorAll + Array.from().find() (O(n)) instead of querySelector('[data-turn-id="..."]') (O(1)) Replace with direct querySelector using entry.id
MessageList.tsx:1819 Resize listener without debounce — each resize triggers getComputedStyle + getBoundingClientRect + setState Throttle with requestAnimationFrame
MessageList.tsx:1831 Tooltip clamping only adjusts top, no horizontal clamping — left: rect.right + 8 can overflow viewport on narrow screens Add horizontal clamp or flip logic when rect.right + tooltipWidth > window.innerWidth
MessageList.module.css z-index: 999 is below project convention (DialogShell: 1000, WebShellSidebar: 1000+, ChatEditor popover: 1000) Use z-index: calc(var(--web-shell-tooltip-z-index, 1000) + 1)
MessageList.dom.test.tsx onMouseLeave on <nav> and per-button onMouseEnter/onMouseLeave never tested — all tooltip tests use focus events only Add mouse event dispatch tests for show/hide
MessageList.dom.test.tsx No test asserts role="tooltip" on the portal element — a11y contract unverified Add expect(detail?.getAttribute('role')).toBe('tooltip')
MessageList.dom.test.tsx useLayoutEffect cleanup (cancelAnimationFrame + clearTimeout + programmaticScrollRef=false) untested with rapid currentIndex changes Add test that re-renders without await nextFrame() between index changes

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Runtime verification of #6526 (real daemon + browser, 81-turn session)

Verdict: the three reported bugs are real and this PR fixes all of them. I found 3 issues in the fix itself — one of which I'd fix before merging, because it defeats one of the PR's own stated goals on any viewport shorter than ~970px.

How I verified (worktree, A/B method, no mocked DOM)
  • Worktree at PR head fdbd9ab1c; merge-base with main is 8296ce9e5. main has not touched either source file since, and the PR is MERGEABLE.
  • Real qwen serve daemon + real vite web-shell dev server + headless Chromium (Playwright), dark theme.
  • An 81-turn session driven through the daemon REST API (POST /session/:id/prompt) against a mock OpenAI-compatible provider, then loaded via /session/<id>?token=.
  • A/B by swapping only MessageList.tsx + MessageList.module.css between merge-base and PR head. Everything else — daemon, transcript, node_modules, browser — is byte-identical between arms, so every delta below is attributable to this PR.
  • Candidate fixes were validated the same way: apply the one-line change on top of the PR, re-measure.

What the PR fixes — confirmed

Claim Evidence (1680×1000, 81 turns)
Native scrollbar hidden on the new rail viewport Computed style on .sessionTimelineViewport: scrollbar-width: none, ::-webkit-scrollbar { display: none; width: 0px }. Strip the two override blocks from the same build and it becomes scrollbar-width: thin, ::-webkit-scrollbar { display: inline; width: 8px } (inherited from .app *::-webkit-scrollbar). The double-class specificity trick is load-bearing.
Rail scrolls internally scrollHeight 1267 / clientHeight 640, scrollTop auto-set to 627 (= max) to centre the current turn
First/last markers reachable Rail scrollTop → 0 reveals marker 0; clicking it drives the transcript scrollTop 14775 → 0 with "Q1 …" in view. On main marker 0 sits at y = -178.5 — outside the transcript's clip rect, so it cannot be hovered or clicked at all.
Rail scroll doesn't move the transcript Wheel over the rail: rail 657 → 257 → 0, transcript scrollTop unchanged at 14775 even after a 3000px over-scroll (overscroll-behavior: contain works)
Tooltip no longer clipped main clips the top-most visible marker's tooltip by 31.9px (title line gone). PR: one role="tooltip" node portalled to <body>, position: fixed, z-index: 1002, 0px off-viewport at the first / middle / last marker and at 700px viewport height.
Fewer DOM nodes [data-testid=session-timeline-detail] count: 81 → 1
a11y aria-describedby set on exactly the hovered/focused marker, removed on blur
Reduced motion animation-name: none under prefers-reduced-motion: reduce

tooltip clipping fixed

Gates: MessageList.dom.test.tsx 40/40 pass. Revert-proof: keep the PR's test file, revert both source files to merge-base → 5 tests fail (all 4 new ones + the modified one), so the new tests are load-bearing, not vacuous. Whole web-shell client suite: 75 files / 1192 tests pass. tsc -p tsconfig.lib.json --noEmit produces the same 218 errors on both arms (all worktree env noise from unbuilt sibling dists; zero in MessageList.*). Prettier 3.6.1 clean.


Finding 1 — top: 50vh makes the rail overflow the transcript; the current-turn marker disappears below ~860px viewport height

MessageList.module.css:185 anchors the sticky layer at top: 50vh, i.e. it centres the rail on the browser viewport. But the rail's clip rect is .list, the transcript scroll container, which ends ~154px above the window bottom (the composer). So the bottom of the rail is clipped away.

clipBottom = panel.bottom − list.bottom (positive ⇒ clipped), 1680px wide, 81 turns:

viewport clipBottom current-turn marker visible last marker visible
1920×1080 −54px
1680×1000 −14px
1680×972 0px
1512×860 (14" MBP viewport) +56px
1680×800 +56px
1440×790 (13" MBP viewport) +56px

The threshold is ~972px. Below it the rail is cut; below ~860px the current-turn marker is never painted — which contradicts the PR's own goal ("the current marker is kept visible"). It gets worse as the composer grows with a multi-line prompt. This is an incomplete fix rather than a fresh regression (main clips 178.5px off both ends at 1680×1000), but it's avoidable.

Suggested fix — revert the layer to top: 50%. Now that the rail has a bounded height, a sticky top: 50% resolves against .list's content box and centres the rail on the transcript. Verified on top of this PR:

  • panelCenterY == listCenterY exactly at 700 / 800 / 900 / 1000px
  • clipTop and clipBottom both negative (slack) at all four heights
  • current + last markers visible at all four heights

Worth reconsidering max-height: min(640px, max(220px, calc(100vh - 220px))) (:209) for the same reason — it is sized against 100vh, not against the scroller it lives in.

rail clipping


Finding 2 — the portalled tooltip loses the app font and renders in Times New Roman

createPortal(…, document.body) (MessageList.tsx:1881-1909) moves the tooltip outside .app, and .app { font-family: var(--font-sans) } is the only thing that sets the app font. Nothing in .sessionTimelineDetails* declares font-family, so the tooltip inherits the UA default.

Measured on the rendered tooltip: font-family: "Times New Roman"; font-size: 16px vs the app's -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; 14px. (You can see it in the "AFTER" panel of the first screenshot above.)

Note SESSION_TIMELINE_TOOLTIP_THEME_VARS (:1686) already copies --font-sans onto the tooltip's inline style — but no rule consumes it, so the copy is dead today. One line makes it live:

.sessionTimelineDetails {
  font-family: var(--font-sans, system-ui, sans-serif);
}

Verified: computed font becomes the app font.

tooltip font

Two related nits on that same var list:

  • --web-shell-popover-z-index is only ever consumed (always with the 1000 fallback) and never defined anywhere in the repo, so calc(var(…, 1000) + 2) always resolves to 1002 today. Copying it is defensive but currently inert — fine to keep, just noting it isn't doing anything.
  • The hand-maintained list is a trap: any var() added to the tooltip subtree later will silently break. Portalling into the theme root instead of document.body would remove the whole mechanism. I checked the .app element: transform: none, filter: none, contain: none, container-type: normal — it does not establish a containing block for position: fixed, and its overflow: hidden does not clip fixed descendants. It also carries the _themeDark_/_themeLight_ class. So a portal into it inherits every var and the font, and position: fixed still behaves exactly as it does today.

Finding 3 — the programmaticScrollRef guard orphans the tooltip for keyboard users

Commit fdbd9ab added a guard so the auto-recentring scroll doesn't dismiss a visible tooltip. Measured behaviour:

  • Pointer: the guard has no effect. When the rail scrolls under a stationary cursor, Chromium re-fires boundary events, so React's onMouseLeave/onMouseEnter re-anchor the tooltip to whatever marker is now under the pointer (measured tooltip↔marker offset: 0px). Correct either way.
  • Keyboard focus: there are no boundary events. Focus marker user-7, then let a new turn arrive → the rail recenters (scrollTop 0 → 642), the tooltip stays at its captured top: 270.5, and its own marker moves to y = −371.5. The tooltip ends up 642px from the turn it describes, floating next to an unrelated marker, with aria-describedby still pointing at it. The same staleness shows up on window resize (1000 → 700: tooltip stays at 235.6, its marker moves to −618).

So the guard converts "tooltip disappears" (harmless) into "tooltip points at the wrong turn" (misleading). Suggestion: on programmatic recentre, re-measure from the anchor element (call showTooltip(entry, anchorEl) again) or hide the tooltip when its anchor leaves the rail viewport.

orphan tooltip


Not covered

  • Firefox / Safari. Only Chromium is installed here, so the scrollbar-width: none (Gecko) path is verified by computed style only, and the WebKit pseudo-element path only under Blink.
  • Scrollbar pixels. Headless Chromium uses overlay scrollbars — offsetWidth − clientWidth is 0 in both arms — so no screenshot can show the scrollbar appearing/disappearing. The computed-style A/B above is the proof; your macOS Chrome check covers the visual.
  • The @media (max-width: 720px) rule wasn't exercised independently: the rail is already hidden at that width by the existing container ≥ 1160px gate.

Nice work — the portal + bounded viewport is the right shape for this. Finding 1 is a one-word change and I'd take it before merge; Findings 2 and 3 are small and can land here or as a follow-up.

中文版

#6526 的真实运行时验证(真实 daemon + 浏览器,81 轮长会话)

结论:PR 描述的三个问题都真实存在,本 PR 都修好了。但我在修复本身里发现了 3 个问题,其中第 1 个建议合并前处理——在视口高度低于约 970px 时,它恰好破坏了本 PR 自己声明的目标之一。

验证方式:worktree 检出 PR head fdbd9ab1c(与 main 的 merge-base 为 8296ce9e5main 之后没有改动这两个源文件,PR 状态 MERGEABLE)。真实 qwen serve daemon + 真实 vite dev server + 无头 Chromium(Playwright),暗色主题。通过 daemon REST 接口用 mock OpenAI 兼容 provider 灌了一个 81 轮的会话,再用 /session/<id>?token= 打开。A/B 只替换 MessageList.tsxMessageList.module.css 两个文件,其余(daemon、transcript、node_modules、浏览器)完全一致,因此下面每一处差异都可归因于本 PR。候选修复也用同样方式验证:在 PR 之上打一行改动再测一次。

已确认修好的部分

声明 证据(1680×1000,81 轮)
新增 rail viewport 隐藏原生滚动条 .sessionTimelineViewport computed:scrollbar-width: none::-webkit-scrollbar { display: none; width: 0px }。把两段覆盖规则从同一构建里删掉后变成 scrollbar-width: thin::-webkit-scrollbar { display: inline; width: 8px }(继承自 .app *::-webkit-scrollbar)。双类名提高优先级的写法是必需的。
rail 内部滚动 scrollHeight 1267 / clientHeight 640scrollTop 自动设为 627(即最大值)以居中当前轮次
首/末 marker 可达 rail scrollTop → 0 后 marker 0 出现;点击它使正文 scrollTop 14775 → 0,"Q1 …" 进入视野。在 main 上 marker 0 位于 y = -178.5,在正文裁剪区之外,根本无法 hover 或点击
rail 滚动不带动正文 在 rail 上滚轮:rail 657 → 257 → 0,正文 scrollTop 始终为 14775,即使继续过量滚动 3000px(overscroll-behavior: contain 生效)
详情浮层不再被裁剪 main 上最靠上的可见 marker 的 tooltip 被裁掉 31.9px(标题整行消失)。PR:单个 role="tooltip" 节点 portal 到 <body>position: fixedz-index: 1002,在首/中/末 marker 以及 700px 视口高度下均 0px 溢出。
DOM 节点数 [data-testid=session-timeline-detail]81 → 1
无障碍 aria-describedby 精确挂在 hover/focus 的那个 marker 上,blur 后移除
减弱动画 prefers-reduced-motion: reduceanimation-name: none

质量门禁MessageList.dom.test.tsx 40/40 通过。反证(revert-proof):保留 PR 的测试文件、把两个源文件回退到 merge-base → 5 个测试失败(4 个新增 + 1 个被修改的),说明新测试确实有效而非空转。web-shell 全量 client 测试:75 文件 / 1192 测试通过tsc -p tsconfig.lib.json --noEmit 在两侧产生完全相同的 218 个报错(都是 worktree 里 sibling dist 未构建造成的环境噪音,MessageList.* 零报错)。Prettier 3.6.1 检查通过。

问题 1 — top: 50vh 让 rail 溢出正文容器;视口高度低于约 860px 时当前轮次 marker 完全不可见

MessageList.module.css:185 把 sticky layer 锚在 top: 50vh,也就是相对浏览器视口居中。但 rail 的裁剪矩形是 .list(正文滚动容器),它的底边在窗口底部上方约 154px(输入框占位)。于是 rail 底部被裁掉。

clipBottom = panel.bottom − list.bottom(为正即被裁),宽 1680px,81 轮:

视口 clipBottom 当前轮 marker 可见 末尾 marker 可见
1920×1080 −54px
1680×1000 −14px
1680×972 0px
1512×860(14" MBP 视口) +56px
1680×800 +56px
1440×790(13" MBP 视口) +56px

临界点约 972px。低于它 rail 被裁;低于约 860px 当前轮次的 marker 根本不会被绘制——这与 PR 自己声明的"当前 marker 保持可见"相矛盾。多行输入让输入框变高时会更严重。这属于修复不彻底而非新引入的回归(main 在 1680×1000 下上下各裁掉 178.5px),但完全可以避免。

建议修复 —— 把 layer 改回 top: 50% 既然 rail 现在有了高度上限,sticky 的 top: 50% 会相对 .list 的 content box 解析,从而相对正文居中。已在本 PR 之上验证:

  • 700 / 800 / 900 / 1000px 下 panelCenterY == listCenterY 完全相等
  • 四种高度下 clipTopclipBottom 均为负(有余量)
  • 四种高度下当前 marker 与末尾 marker 都可见

同理建议重新考虑 max-height: min(640px, max(220px, calc(100vh - 220px))):209)——它是按 100vh 而不是按它所在的滚动容器来计算的。

问题 2 — portal 出去的 tooltip 丢失了 app 字体,退化成 Times New Roman

createPortal(…, document.body)MessageList.tsx:1881-1909)把 tooltip 移出了 .app,而 .app { font-family: var(--font-sans) } 是唯一设置 app 字体的地方。.sessionTimelineDetails* 里没有任何 font-family 声明,于是 tooltip 继承了浏览器默认字体。

实测渲染出来的 tooltip:font-family: "Times New Roman"; font-size: 16px,而 app 是 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; 14px。(在上面第一张截图的 "AFTER" 面板里就能看出来。)

注意 SESSION_TIMELINE_TOOLTIP_THEME_VARS:1686已经--font-sans 复制到了 tooltip 的行内样式上——但没有任何规则消费它,所以这次复制目前是死代码。一行即可让它生效:

.sessionTimelineDetails {
  font-family: var(--font-sans, system-ui, sans-serif);
}

已验证:computed 字体变回 app 字体。

关于同一份变量清单的两点补充:

  • --web-shell-popover-z-index 在整个代码库里只被读取(且始终带 1000 兜底)、从未被定义过,所以 calc(var(…, 1000) + 2) 目前恒为 1002。复制它属于防御性写法,但当前不起作用——保留无妨,只是说明它现在没有实际效果。
  • 这份手工维护的清单本身是个坑:以后往 tooltip 子树里加任何 var() 都会静默失效。把 portal 目标从 document.body 换成主题根节点即可彻底去掉这套机制。 我检查过 .app 元素:transform: nonefilter: nonecontain: nonecontainer-type: normal——它不会position: fixed 建立包含块,其 overflow: hidden 也不裁剪 fixed 后代;同时它带着 _themeDark_/_themeLight_ 类名。所以 portal 到它上面能继承全部变量和字体,而 position: fixed 的行为与现在完全一致。

问题 3 — programmaticScrollRef 守卫让键盘用户看到"孤儿" tooltip

fdbd9ab 加了守卫,使自动居中的滚动不会关掉已显示的 tooltip。实测行为:

  • 鼠标:守卫其实没起作用。当 rail 在静止的光标下滚动时,Chromium 会重新派发边界事件,React 的 onMouseLeave/onMouseEnter 会把 tooltip 重新锚定到光标下的新 marker(实测 tooltip 与 marker 偏移 0px)。两种情况下结果都正确。
  • 键盘焦点:没有边界事件。聚焦 marker user-7,随后新一轮到达 → rail 重新居中(scrollTop 0 → 642),tooltip 停在原先记录的 top: 270.5,而它自己的 marker 移动到 y = −371.5tooltip 与它所描述的轮次相距 642px,悬浮在一个毫不相干的 marker 旁边,而 aria-describedby 仍然指向它。窗口 resize 时也有同样的失效(1000 → 700:tooltip 停在 235.6,marker 移到 −618)。

所以这个守卫把"tooltip 消失"(无害)变成了"tooltip 指向错误的轮次"(有误导性)。建议:在程序化重新居中时,用锚点元素重新测量(再调一次 showTooltip(entry, anchorEl)),或在锚点离开 rail viewport 时隐藏 tooltip。

未覆盖的部分

  • Firefox / Safari:本机只装了 Chromium,因此 scrollbar-width: none(Gecko)路径只做了 computed style 层面的验证,WebKit 伪元素路径也只在 Blink 下验证。
  • 滚动条像素:无头 Chromium 使用 overlay 滚动条——两侧 offsetWidth − clientWidth 都是 0——所以任何截图都无法展示滚动条的出现/消失。上面的 computed style A/B 就是证据;你在 macOS Chrome 上的目视检查覆盖了视觉部分。
  • @media (max-width: 720px) 规则没有被独立验证:在该宽度下现有的 container ≥ 1160px 门槛已经把 rail 隐藏了。

整体做得不错——portal + 有界 viewport 是正确的方向。问题 1 只需改一个词,建议合并前处理;问题 2、3 影响较小,在本 PR 里改或后续跟进都可以。

@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the detailed verification. For Finding 1, I am intentionally keeping the current behavior.

The session timeline is designed to be visually centered against the page/viewport, not centered inside the transcript body. Centering it against the transcript area makes the rail sit too high once the composer takes space at the bottom, which is the behavior this PR is deliberately avoiding.

The small bottom overlap at shorter viewport heights is an acceptable tradeoff for this design. The rail itself is scrollable, so users can still scroll the timeline viewport to reach and select later markers. This matches the intended Codex-style interaction: the timeline remains visually centered in the user's view, while dense marker lists are navigated within the rail.

So I do not think Finding 1 is a bug that needs to be fixed in this PR. Findings 2 and 3 were valid and have been addressed in edda10b57.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification: local build + real-browser A/B

I built this branch locally and drove the real MessageList component in a real Chromium (Playwright), comparing the PR against its merge base (8296ce9e5) side by side with an 80-turn session. The harness mounts the actual component under the app's real DOM/CSS chain (.app.themeDark > .appShell > .content > .list + a real .footer composer box), with theme variables deliberately left off :root — exactly as in the real app, since --background/--foreground are scoped to the .app.themeDark wrapper.

Verdict: the behavioural core is correct and genuinely fixes the reported bugs. One CSS change (top: 50vh) is a net regression and should be reverted before merge.


✅ Gates

Check Result
npm run build --workspace=packages/web-shell (incl. tsc -p tsconfig.lib.json) pass
npx vitest run (whole web-shell package) 1265 passed / 79 files
MessageList.dom.test.tsx 41 passed (PR body says 38)
eslint on the 3 changed files 0 errors
Console / page errors during browser runs 0

✅ Confirmed fixes (measured, not eyeballed)

  1. Earliest/latest turns were genuinely unreachable, and now aren't. On main, only 52 of 80 markers hit-test to themselves; markers 0–13 and 66–79 sit outside the chat scrollport. Wheeling over the rail scrolls the chat transcript by −5000px and never reveals them. On this PR the rail scrolls internally (scrollTop 612→0), the chat scroll delta is 0 (overscroll-behavior: contain works), and marker 0 becomes hit-testable with a working tooltip.

  2. Tooltip clipping was real. On main, the topmost usable marker's tooltip is clipped 33.4px by .list's overflow-y: auto — the entire title row is sliced off. Same at the bottom. On this PR, clipping is 0px at every marker: position: fixed, portaled to document.body, z-index: 1002.

  3. The scrollbar-hiding CSS has teeth. Computed on the rail viewport: scrollbar-width: none, scrollbar-color: transparent transparent, ::-webkit-scrollbar { display: none; width: 0; height: 0 }. Deleting exactly the three rules this PR adds hands the element back to the app's global .app * rules → scrollbar-width: thin, ::-webkit-scrollbar { display: inline; width: 8px }, gutter 0 → 10px, and 1,882 pixels of #3a3a3a (--scrollbar-thumb) paint in the rail's right-hand strip. So the doubled-class + !important really is needed to beat .app *. (Headless Chromium here cannot paint scrollbars at all — I verified that with a forced 20px green control scrollbar, then re-ran headed under Xvfb for the pixel check.)

  4. Theming survives the portal. Since the tooltip now lives in document.body, outside .app.themeDark, the themeVars copy is load-bearing — and it works: title rgb(250,250,250), detail #808598 @ 86%, background #171717.

  5. Tooltip re-anchors correctly under a held hover while the chat scrolls — it tracks the marker actually under the cursor (turn 52), not a stale one.

  6. DOM/a11y win: idle detail nodes go from 80 → 0; 80 aria-hidden="true" blocks are replaced by one role="tooltip" + aria-describedby.

  7. No regressions on the existing gates: the rail is still absent below the 1160px container threshold (checked at 1000px and 600px) and for sessions under 4 entries; a 4-entry session renders non-scrollable with all 4 markers usable.

  8. The 5 new tests have teeth. Four independent mutations of the shipped code, each caught by exactly the intended test (41/41 restored after each revert):

    Mutation Test that fails
    render the tooltip inline instead of createPortal renders timeline details as one body-level tooltip…
    handleViewportScroll → no-op …hides them on user scroll + hides timeline details when the user scrolls…
    isTooltipAnchorVisible → always true hides timeline details when the focused marker moves out of view
    drop the viewport.scrollTop = … auto-centring write keeps timeline details during current-turn centering…

🔴 Should fix before merge: top: 50%top: 50vh de-centers the rail and clips its bottom

.sessionTimelineLayer is position: sticky inside .list, which is the chat scrollport. A percentage top resolves against that scrollport, so the old top: 50% put the rail exactly at the chat area's centre. 50vh resolves against the browser viewport, which is taller than the chat area by the composer's height — so the rail is pushed down by roughly half the composer.

The composer is not small: ChatEditor.module.css sets height: 140px at rest and --chat-editor-input-max-height: 300px, so a footer of 150–360px is the ordinary range.

Rail centre offset from the chat area's centre (px, + = sits too low; headed Chromium, 80 turns, 1440px wide):

viewport × composer main (50%) this PR (50vh) this PR + top: 50%
900 × 132 0 +78 0
900 × 200 0 +112 0
900 × 260 0 +142 0
900 × 360 0 +192 0
800 × 180 0 +102 0
700 × 132 0 +78 0
700 × 260 0 +142 0

Rail bottom clipped by the chat scrollport (px) / is the current turn's marker clickable:

viewport × composer main this PR this PR + top: 50%
900 × 132 210 / no 14 / yes 0 / yes
900 × 200 244 / no 82 / no 0 / yes
900 × 260 274 / no 142 / no 0 / yes
900 × 360 324 / no 242 / no 50 / no
800 × 180 284 / no 82 / no 0 / yes
700 × 132 310 / no 34 / no 0 / yes
700 × 260 374 / no 162 / no 20 / yes

So the marker for the current turn — the one the new auto-centring code deliberately scrolls into view — stops being clickable in 6 of the 7 window/composer sizes I tested. Reverting only that one property, keeping every other line of this PR, restores exact centring and makes the last marker usable in 6/7. This also explains why it looked fine in your macOS Chrome check: at a tall viewport with an idle composer (the 900 × 132 row) it's the one configuration that still works.

 .sessionTimelineLayer {
   position: sticky;
-  top: 50vh;
+  top: 50%;

Optional hardening: max-height: min(640px, max(220px, calc(100vh - 220px))) is window-relative for the same reason. With top: 50% restored, the only remaining clipped case is a 900px viewport with a fully-expanded 360px composer (50px). Sizing that cap against the chat scrollport instead of 100vh would close it.

🟡 Minor: the tooltip paints above modal dialogs

--web-shell-popover-z-index has no default definition, so calc(var(--web-shell-popover-z-index, 1000) + 2) = 1002. DialogShell is also createPortal(…, document.body) with .backdrop { z-index: 1000 }. Verified with a pixel probe: with an opaque z-1000 overlay covering the screen, the pixel at the tooltip's centre is the tooltip's own background. Usually harmless — opening a dialog moves focus and onBlur hides the tooltip — but a dialog raised while the pointer merely rests on a marker (e.g. an incoming tool-approval) will render under it.

🟡 Nit: the tooltip container's color is black

The portal escapes .sessionTimelinePanel { color: var(--muted-foreground) }, and color isn't in SESSION_TIMELINE_TOOLTIP_THEME_VARS. Every text child sets its own colour, so nothing renders black today — but .sessionTimelineDetails computes color: rgb(0, 0, 0) against a #171717 background. Adding color: var(--foreground) to the rule makes it safe for anyone who later adds a bare text node.

📋 Note on coverage

None of the 41 jsdom tests can observe top, max-height, the mask, or the scrollbar — jsdom has no layout engine. The five new tests pin the JavaScript behaviour well, but the layout half of this PR is invisible to CI, which is exactly how the 50vh issue got through. Worth keeping in mind for future timeline changes.


中文版

维护者验证:本地构建 + 真实浏览器 A/B 对比

我在本地构建了这个分支,并在真实 Chromium 中运行真实的 MessageList 组件(Playwright),用 80 轮长会话把 PR 和它的 merge base(8296ce9e5)并排对比。测试环境挂载了真实组件,并复刻了应用真实的 DOM/CSS 结构(.app.themeDark > .appShell > .content > .list 加一个真实的 .footer 输入框盒子);主题变量故意不放在 :root 上——这与真实应用一致,因为 --background/--foreground 是作用在 .app.themeDark 这个包裹元素上的。

结论:行为层面的核心改动是正确的,确实修复了所描述的问题。但其中一处 CSS 改动(top: 50vh)是净负面的,建议合并前回退。


✅ 各项检查

检查项 结果
npm run build --workspace=packages/web-shell(含 tsc -p tsconfig.lib.json 通过
npx vitest run(整个 web-shell 包) 1265 通过 / 79 个文件
MessageList.dom.test.tsx 41 通过(PR 描述里写的是 38)
对 3 个改动文件跑 eslint 0 错误
浏览器运行期间的 console / page 报错 0

✅ 已确认修复的问题(都是实测,不是肉眼估计)

  1. 最早/最新的轮次此前确实点不到,现在可以了。main 上,80 个 marker 里只有 52 个能命中自身;marker 0–13 和 66–79 落在聊天滚动容器之外。在时间轴上滚滚轮会让聊天正文滚动 −5000px,而且永远滚不出那些 marker。在这个 PR 上,时间轴内部滚动(scrollTop 612→0),聊天滚动增量为 0overscroll-behavior: contain 生效),marker 0 变为可命中且 tooltip 正常显示。

  2. Tooltip 裁剪问题是真实存在的。main 上,最顶部可用 marker 的 tooltip 会被 .listoverflow-y: auto 裁掉 33.4px——整个标题行被切没了,底部同理。在这个 PR 上,所有 marker 的裁剪量都是 0pxposition: fixed,portal 到 document.bodyz-index: 1002

  3. 隐藏滚动条的 CSS 是有效的(有"牙齿")。 时间轴 viewport 的计算样式为:scrollbar-width: nonescrollbar-color: transparent transparent::-webkit-scrollbar { display: none; width: 0; height: 0 }。把这个 PR 新增的那三条规则精确删掉之后,该元素就会回落到应用全局的 .app * 规则 → scrollbar-width: thin::-webkit-scrollbar { display: inline; width: 8px },gutter 从 0 变成 10px,并且时间轴右侧条带里会画出 1882 个 #3a3a3a 像素--scrollbar-thumb)。所以双写类名 + !important 确实是为了压过 .app *,必要。(这里的 headless Chromium 根本画不出滚动条——我用一个强制 20px 绿色滚动条的对照组验证了这一点,之后改成在 Xvfb 下用有头模式来做像素级检查。)

  4. 主题能穿过 portal 边界。 tooltip 现在位于 document.body,在 .app.themeDark 之外,所以 themeVars 的复制是关键的——而且它确实生效了:标题 rgb(250,250,250),详情 #808598 @ 86%,背景 #171717

  5. 在按住 hover 的同时滚动聊天,tooltip 会正确重新锚定——它跟随的是光标下方真实的那个 marker(第 52 轮),而不是过期的那个。

  6. DOM / 无障碍收益: 空闲时的 detail 节点从 80 → 0;80 个 aria-hidden="true" 的块被一个 role="tooltip" + aria-describedby 取代。

  7. 没有回归:低于 1160px 容器阈值时时间轴依然不渲染(在 1000px 和 600px 下都验证过),少于 4 个条目的会话也一样;4 条目的会话正常渲染、不可滚动、4 个 marker 全部可用。

  8. 5 个新增测试是有效的。 我对已合入的源码做了 4 处独立变异,每一处都被恰好对应的那个测试抓到(每次回退后都恢复 41/41):

    变异 失败的测试
    把 tooltip 改成内联渲染,去掉 createPortal renders timeline details as one body-level tooltip…
    handleViewportScroll → 空函数 …hides them on user scroll + hides timeline details when the user scrolls…
    isTooltipAnchorVisible → 恒为 true hides timeline details when the focused marker moves out of view
    去掉 viewport.scrollTop = … 这一句自动居中写入 keeps timeline details during current-turn centering…

🔴 合并前建议修复:top: 50%top: 50vh 会让时间轴偏离居中并被裁掉底部

.sessionTimelineLayer.list 内部的 position: sticky 元素,而 .list 就是聊天滚动容器。百分比形式的 top 是相对该滚动容器解析的,所以旧的 top: 50% 恰好把时间轴放在聊天区域的正中心。而 50vh 是相对浏览器视口解析的,视口比聊天区域高出一个输入框的高度——于是时间轴被往下推了大约半个输入框。

输入框并不矮:ChatEditor.module.css 里静止高度是 height: 140px,且 --chat-editor-input-max-height: 300px,因此 150–360px 的 footer 高度属于正常范围。

时间轴中心相对聊天区域中心的偏移(px,+ 表示偏低;有头 Chromium,80 轮,宽 1440px):

视口 × 输入框 main50% 本 PR(50vh 本 PR + top: 50%
900 × 132 0 +78 0
900 × 200 0 +112 0
900 × 260 0 +142 0
900 × 360 0 +192 0
800 × 180 0 +102 0
700 × 132 0 +78 0
700 × 260 0 +142 0

时间轴底部被聊天滚动容器裁掉的像素数 / 当前轮次的 marker 是否可点击:

视口 × 输入框 main 本 PR 本 PR + top: 50%
900 × 132 210 / 否 14 / 0 / 是
900 × 200 244 / 否 82 / 0 /
900 × 260 274 / 否 142 / 0 /
900 × 360 324 / 否 242 / 否 50 / 否
800 × 180 284 / 否 82 / 0 /
700 × 132 310 / 否 34 / 0 /
700 × 260 374 / 否 162 / 20 /

也就是说,当前轮次的 marker——正是新增的自动居中逻辑特意要滚动到可见的那一个——在我测试的 7 种视口/输入框组合里有 6 种变得不可点击。只回退这一个属性、保留本 PR 其余全部改动,就能恢复精确居中,并让最后一个 marker 在 7 种里的 6 种下可用。这也解释了为什么你在 macOS Chrome 上看起来是正常的:高视口 + 静止输入框(即 900 × 132 那一行)恰好是唯一还能正常工作的配置。

 .sessionTimelineLayer {
   position: sticky;
-  top: 50vh;
+  top: 50%;

可选的进一步加固:max-height: min(640px, max(220px, calc(100vh - 220px))) 出于同样的原因也是相对窗口的。恢复 top: 50% 之后,唯一还会被裁剪的场景是 900px 视口 + 完全展开的 360px 输入框(裁掉 50px)。把这个上限改成相对聊天滚动容器而非 100vh 来计算,就能彻底堵上。

🟡 次要:tooltip 会盖在模态对话框之上

--web-shell-popover-z-index 没有任何默认定义,所以 calc(var(--web-shell-popover-z-index, 1000) + 2) = 1002。而 DialogShell 同样createPortal(…, document.body),且 .backdrop { z-index: 1000 }。我用像素探针验证过:铺一个不透明的 z-1000 遮罩盖满屏幕后,tooltip 中心位置的像素仍然是 tooltip 自身的背景色。通常没什么影响——打开对话框会移动焦点,onBlur 会把 tooltip 收起——但如果只是鼠标停在某个 marker 上时弹出了对话框(例如收到一个工具审批请求),tooltip 就会浮在对话框上方。

🟡 吹毛求疵:tooltip 容器的 color 是黑色

portal 逃出了 .sessionTimelinePanel { color: var(--muted-foreground) } 的作用域,而 color 并不在 SESSION_TIMELINE_TOOLTIP_THEME_VARS 里。由于每个文字子元素都各自设置了颜色,所以目前不会渲染出黑字——但 .sessionTimelineDetails 的计算值是 color: rgb(0, 0, 0),而背景是 #171717。在该规则里补一条 color: var(--foreground),就能避免以后有人往里加裸文本节点时踩坑。

📋 关于测试覆盖的说明

41 个 jsdom 测试没有一个能观察到 topmax-height、遮罩或滚动条——jsdom 没有布局引擎。新增的 5 个测试把 JavaScript 行为钉得很牢,但这个 PR 的布局部分对 CI 是完全不可见的,50vh 这个问题正是这样漏过去的。后续改动时间轴时值得留意这一点。

@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the extra verification. I addressed the two independent tooltip hardening points in 38cc27834:

  • lowered the timeline tooltip to z-index: 999, so it stays above the page content but below DialogShell's z-1000 modal backdrop;
  • added color: var(--foreground) on the tooltip container so future bare text does not inherit the body default color.

Verification after that change:

  • npx prettier --check packages/web-shell/client/components/MessageList.module.css
  • npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx => 41 passed
  • npm run build --workspace=packages/web-shell => passed

For the top: 50vh point, I am still intentionally keeping the viewport/page-centered rail. The goal here is for the timeline to remain visually centered in the user's view rather than centered inside the transcript body's remaining area; using top: 50% moves it too high when the composer occupies the bottom of the page. The rail is independently scrollable, so the short-viewport bottom overlap remains an accepted tradeoff for this design rather than a bug to fix in this PR.

Comment thread packages/web-shell/client/components/MessageList.tsx Outdated
Comment thread packages/web-shell/client/components/MessageList.tsx
Comment thread packages/web-shell/client/components/MessageList.tsx
Comment thread packages/web-shell/client/components/MessageList.dom.test.tsx
@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the actionable parts in 20ce1e193.

What changed:

  • Replaced the programmatic-scroll guard reset from requestAnimationFrame to setTimeout(0), so the guard remains active through the browser's queued scroll event even on engines that might order that event later than Chromium.
  • Updated the long-session test to assert the timeline viewport's internal scrollTop directly, instead of relying only on the parent scrollIntoView jump after clicking a marker.
  • Added a focused clamp regression test that mocks the tooltip rect above and below the viewport edge and asserts the adjusted style.top values.

Verification:

  • Real Chromium ordering check for scrollTop showed scroll event flag=true before the rAF callback, so the exact rAF-before-scroll claim does not reproduce in Chromium; still, the setTimeout(0) change is a harmless cross-browser hardening.
  • npx prettier --check packages/web-shell/client/components/MessageList.tsx packages/web-shell/client/components/MessageList.dom.test.tsx packages/web-shell/client/components/MessageList.module.css => pass
  • npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx => 42 passed
  • npm run build --workspace=packages/web-shell => pass

I did not add exhaustive tests for every early-return branch in the centering effect. Those branches are defensive no-op paths rather than distinct user-visible behaviors, and covering all of them would add a lot of brittle jsdom scaffolding without changing the product behavior this PR is fixing.

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Re-verification of 38cc27834

Re-ran the same local build + real-browser A/B against the updated head. The new commit is CSS-only (2 lines in MessageList.module.css).

Both minor items are fixed and verified. The blocking item — top: 50vh — is unchanged, and re-measures identically.

✅ Fixed and confirmed

Item Before Now (38cc27834)
Tooltip container color rgb(0, 0, 0) on a #171717 background rgb(250, 250, 250)color: var(--foreground) resolves correctly through the portal
Tooltip vs modal DialogShell (.backdrop, z-index: 1000) painted above the modal below
Tooltip vs .slashPanel / .atPanel (z-index: 1000) above below
Tooltip vs .slashDetail (z-index: 1001) above below
Tooltip vs ordinary chat content above still above ✅ (checked with a z-index: 998 control: tooltip wins)

Paint order was measured with an opaque full-screen overlay portaled to document.body at each z-index and sampling the pixel at the tooltip's centre (headed Chromium under Xvfb).

Everything I confirmed last round still holds on the new head: tooltip clipping 0px, portaled to document.body, hides on rail scroll, marker 1 reachable by wheeling the rail with chat scroll delta 0. Gates: 1265 tests pass (79 files), MessageList.dom.test.tsx 41 pass, npm run build --workspace=packages/web-shell passes, 0 page errors.

🔴 Still open: top: 50vh

Re-measured on 38cc27834, 80 turns, 1440px wide, headed Chromium. computed top is 450px at a 900px viewport, 400px at 800px, 350px at 700px — i.e. still 50vh, still resolved against the browser viewport rather than the chat scrollport it is position: sticky inside. The numbers are byte-for-byte the same as my previous run:

viewport × composer rail centre offset from chat centre
main / this PR / this PR + top: 50%
rail bottom clipped (px)
main / PR / PR + 50%
current-turn marker clickable
main / PR / PR + 50%
900 × 132 0 / +78 / 0 210 / 14 / 0 no / yes / yes
900 × 200 0 / +112 / 0 244 / 82 / 0 no / no / yes
900 × 260 0 / +142 / 0 274 / 142 / 0 no / no / yes
900 × 360 0 / +192 / 0 324 / 242 / 50 no / no / no
800 × 180 0 / +102 / 0 284 / 82 / 0 no / no / yes
700 × 132 0 / +78 / 0 310 / 34 / 0 no / no / yes
700 × 260 0 / +142 / 0 374 / 162 / 20 no / no / yes

The current turn's marker — the one the new auto-centring code deliberately scrolls into view — is still unclickable in 6 of the 7 window/composer sizes. Changing only this one property, keeping every other line of the PR, restores exact centring and makes it clickable in 6/7:

 .sessionTimelineLayer {
   position: sticky;
-  top: 50vh;
+  top: 50%;

Was this one intentionally left out, or just missed? If there's a reason 50vh is needed I'd like to hear it — but as measured, 50% is strictly better in every configuration I tried.

🟡 New nit from the z-index change

--web-shell-popover-z-index is still listed in SESSION_TIMELINE_TOOLTIP_THEME_VARS (MessageList.tsx:1692) but the CSS no longer reads it, so it's now dead. It was already a no-op in practice: nothing defines that variable, so getComputedStyle(panel).getPropertyValue('--web-shell-popover-z-index') returns '', and setProperty(name, '') removes the property — I confirmed the rendered tooltip carries no inline value for it. Worth dropping from the array.

Related, and only if you care about embedders: z-index: 999 is now hard-coded, so unlike every other popover in web-shell the tooltip no longer tracks an embedder's --web-shell-popover-z-index override. That's harmless today precisely because DialogShell.backdrop also hard-codes 1000, so I wouldn't hold the PR on it — just flagging the asymmetry.


中文版

38cc27834 的复验

用同样的本地构建 + 真实浏览器 A/B 重新验证了更新后的 head。新提交只改了 CSS(MessageList.module.css 中的 2 行)。

两个次要问题都已修复并验证通过。阻塞项——top: 50vh——没有改动,复测结果完全一致。

✅ 已修复并确认

项目 之前 现在(38cc27834
Tooltip 容器 color #171717 背景上的 rgb(0, 0, 0) rgb(250, 250, 250)——color: var(--foreground) 能正确穿过 portal 解析
Tooltip vs 模态框 DialogShell.backdropz-index: 1000 画在模态框之上 之下
Tooltip vs .slashPanel / .atPanelz-index: 1000 之上 之下
Tooltip vs .slashDetailz-index: 1001 之上 之下
Tooltip vs 普通聊天内容 之上 仍在之上 ✅(用 z-index: 998 的对照组验证:tooltip 胜出)

绘制顺序的测量方式:在每个 z-index 上向 document.body portal 一个不透明的全屏遮罩,然后采样 tooltip 中心点的像素(Xvfb 下的有头 Chromium)。

上一轮确认的结论在新 head 上依然成立:tooltip 裁剪量 0px、portal 到 document.body、时间轴滚动时会隐藏、滚动时间轴滚轮可以到达第 1 个 marker 且聊天滚动增量为 0。各项检查:1265 个测试通过(79 个文件),MessageList.dom.test.tsx 41 个通过npm run build --workspace=packages/web-shell 通过,0 个页面报错。

🔴 仍未解决:top: 50vh

38cc27834 上重新测量,80 轮、宽 1440px、有头 Chromium。computed top 在 900px 视口下是 450px,800px 下是 400px,700px 下是 350px——也就是说仍然是 50vh,仍然是相对浏览器视口解析的,而不是它作为 position: sticky 所处的那个聊天滚动容器。数据和我上一轮跑出来的完全一致:

视口 × 输入框 时间轴中心相对聊天中心的偏移
main / 本 PR / 本 PR + top: 50%
时间轴底部被裁掉的像素
main / PR / PR + 50%
当前轮次 marker 是否可点击
main / PR / PR + 50%
900 × 132 0 / +78 / 0 210 / 14 / 0 否 / / 是
900 × 200 0 / +112 / 0 244 / 82 / 0 否 / /
900 × 260 0 / +142 / 0 274 / 142 / 0 否 / /
900 × 360 0 / +192 / 0 324 / 242 / 50 否 / 否 / 否
800 × 180 0 / +102 / 0 284 / 82 / 0 否 / /
700 × 132 0 / +78 / 0 310 / 34 / 0 否 / /
700 × 260 0 / +142 / 0 374 / 162 / 20 否 / /

当前轮次的 marker——正是新增的自动居中逻辑特意要滚动到可见的那一个——在 7 种视口/输入框组合里仍有 6 种不可点击。只改这一个属性、保留本 PR 其余全部改动,就能恢复精确居中,并让它在 7 种里的 6 种下可点击:

 .sessionTimelineLayer {
   position: sticky;
-  top: 50vh;
+  top: 50%;

这一处是有意保留的,还是漏掉了?如果确实有必须用 50vh 的理由,我很想听听——但就实测数据而言,在我试过的每一种配置下 50% 都严格更优。

🟡 z-index 改动带来的一个新的小问题

--web-shell-popover-z-index 仍然留在 SESSION_TIMELINE_TOOLTIP_THEME_VARS 里(MessageList.tsx:1692),但 CSS 已经不再读取它,所以它现在是死代码。其实它本来就是个空操作:没有任何地方定义这个变量,因此 getComputedStyle(panel).getPropertyValue('--web-shell-popover-z-index') 返回 '',而 setProperty(name, '') 会把该属性移除——我确认了渲染出来的 tooltip 上并没有携带它的内联值。建议从数组里去掉。

相关的一点,仅在你关心嵌入方(embedder)时才有意义:z-index: 999 现在是硬编码的,因此与 web-shell 里其他所有 popover 不同,tooltip 不再跟随嵌入方对 --web-shell-popover-z-index 的覆盖。这在今天是无害的,恰恰因为 DialogShell.backdrop 同样硬编码了 1000,所以我不会因此卡住这个 PR——只是把这个不对称点提出来。

@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for re-checking.

I removed the now-unused --web-shell-popover-z-index entry from the tooltip theme-var copy list in 14f2e75c6.

Verification:

  • npx prettier --check packages/web-shell/client/components/MessageList.tsx packages/web-shell/client/components/MessageList.dom.test.tsx packages/web-shell/client/components/MessageList.module.css => pass
  • npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx => 42 passed
  • npm run build --workspace=packages/web-shell => pass

On top: 50vh: yes, it is intentionally left as-is. The timeline is meant to be visually centered against the page/viewport, not centered within the transcript scrollport after subtracting the composer. Switching to top: 50% makes the rail sit too high in the actual page composition this PR is targeting. The rail remains independently scrollable for dense sessions, so the short-viewport overlap is an accepted tradeoff for that visual placement rather than a missed fix.

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Re-verification of 14f2e75c6

Two new commits since my last pass: 20ce1e193 (harden timeline tooltip recentering) and 14f2e75c6 (drop unused timeline tooltip var). I re-ran the local build and the real-browser A/B.

Summary: the dead-var nit is fixed. top: 50vh is still unchanged. And 20ce1e193 introduces a new regression — swapping requestAnimationFrame for setTimeout(…, 0) breaks the very behaviour the programmaticScrollRef guard exists to protect.

Gates: 1266 tests pass (79 files, +1 for the new clamp test), MessageList.dom.test.tsx 42 pass, build passes, eslint 0 errors, 0 page errors.


✅ Fixed

--web-shell-popover-z-index is gone from SESSION_TIMELINE_TOOLTIP_THEME_VARS. Confirmed.

The new clamps timeline details to the viewport edge test has teeth — disabling the clamping useLayoutEffect fails exactly that test and nothing else.


🔴 New regression: requestAnimationFramesetTimeout(…, 0) in 20ce1e193

-    const frame = requestAnimationFrame(() => {
+    const timer = window.setTimeout(() => {
       programmaticScrollRef.current = false;
       syncTooltip();
-    });
+    }, 0);

The guard's whole purpose is: when we scroll the rail to recentre the current turn, don't treat the resulting scroll event as a user scroll and hide the tooltip. That only works if the flag is still true when the browser dispatches that scroll event.

A scroll event from an imperative scrollTop write is dispatched during "update the rendering", in the scroll steps — which run after timer tasks and before requestAnimationFrame callbacks. So rAF is always safe; setTimeout(0) is a race. Measured directly in Chromium (5 runs of el.scrollTop = N with all three callbacks armed):

scroll -> rAF -> setTimeout0        (2/5)   guard holds under both
setTimeout0 -> scroll -> rAF        (3/5)   guard already cleared when scroll lands  <-- bug

scroll preceded rAF in 5/5 runs. setTimeout(0) preceded scroll in 3/5. And when the write is initiated from inside a rendering-phase callback, the bad order is deterministic.

End-to-end, in the real component. 80-turn session, tooltip held open on marker #36 via keyboard focus (focusin, no pointer involved), then 40 wheel ticks over the transcript. Every tick genuinely recentred the rail, and the focused marker stayed inside the rail viewport the whole time — so every hide is spurious:

Build Rail recentred Spurious hides (tooltip vanished while its anchor was still visible)
shipped — setTimeout(0) 40/40 27 / 40
shipped — setTimeout(0) (rerun) 40/40 32 / 40
shipped — setTimeout(0) (A/B/A, restored) 40/40 12 / 40
mutated back to requestAnimationFrame 40/40 0 / 40
mutated back to requestAnimationFrame (rerun) 40/40 0 / 40
mutated back to requestAnimationFrame (rerun) 40/40 0 / 40

0/120 with rAF, 71/120 with setTimeout(0). The spread across the three shipped runs (27/32/12) is exactly what you'd expect from a race.

Concretely: a keyboard user tabs to a timeline marker to read its tooltip, and the tooltip flickers out on every recentring while the assistant streams (follow-mode keeps changing currentIndex). aria-describedby drops with it. Pointer hover is affected too whenever the rail shifts by less than half a marker pitch, so the pointer stays on the same marker and no mouseleave masks it.

The PR's own test is literally named keeps timeline details during current-turn centering …, so this is a regression against stated intent, not a judgement call.

Suggested fix — revert that hunk. If the motivation was that rAF never fires in a background tab (leaving the flag stuck true), a belt-and-braces version keeps the correct ordering:

const frame = requestAnimationFrame(() => {
  programmaticScrollRef.current = false;
  syncTooltip();
});
const fallback = window.setTimeout(() => {
  programmaticScrollRef.current = false;
}, 100);
return () => {
  cancelAnimationFrame(frame);
  window.clearTimeout(fallback);
  programmaticScrollRef.current = false;
};

⚠️ Why CI didn't catch it

keeps timeline details during current-turn centering but hides them on user scroll passes with both primitives — I ran the file four times with rAF restored: 42/42 every time. jsdom never dispatches a scroll event in response to a scrollTop write, so the test hand-dispatches one and can only ever assert "a scroll after the flag reset hides it". The ordering under test is the one thing it cannot observe. The nextFrame()nextTask() edit in the same commit was therefore not required by a real failure; it just re-tuned the test's wait to the new primitive.

If you want this covered, the assertion has to be that the scroll event produced by the component's own scrollTop write does not hide the tooltip — which needs a real browser, not jsdom.


🔴 Still open: top: 50vh

MessageList.module.css is byte-for-byte unchanged across both new commits (git diff 38cc27834..14f2e75c6 -- …module.css → 0 lines). Re-measured on 14f2e75c6 anyway, headed Chromium, 80 turns, 1440px wide — identical to both previous rounds:

viewport × composer computed top rail centre offset from chat centre rail bottom clipped current-turn marker clickable usable markers
900 × 132 450px +78 14 yes 41/80
900 × 200 450px +112 82 no 37/80
900 × 260 450px +142 142 no 33/80
800 × 180 400px +102 82 no 33/80
700 × 132 350px +78 34 no 29/80
700 × 260 350px +142 162 no 21/80
 .sessionTimelineLayer {
   position: sticky;
-  top: 50vh;
+  top: 50%;

This is the third round I've raised it and it hasn't been touched or answered. If there's a reason 50vh is needed, please say so and I'll drop it — but as measured, 50% is strictly better in every configuration, and it's a one-property change.


中文版

14f2e75c6 的复验

自上一轮以来新增两个提交:20ce1e193harden timeline tooltip recentering)和 14f2e75c6drop unused timeline tooltip var)。我重新跑了本地构建和真实浏览器 A/B。

结论:死变量那个小问题已修复。top: 50vh 仍未改动。而且 20ce1e193 引入了一个新的回归——把 requestAnimationFrame 换成 setTimeout(…, 0),恰好破坏了 programmaticScrollRef 这个守卫本来要保护的行为。

各项检查:1266 个测试通过(79 个文件,新增的 clamp 测试 +1),MessageList.dom.test.tsx 42 个通过,构建通过,eslint 0 错误,0 个页面报错。


✅ 已修复

--web-shell-popover-z-index 已从 SESSION_TIMELINE_TOOLTIP_THEME_VARS 中移除,已确认。

新增的 clamps timeline details to the viewport edge 测试是有效的——把 clamping 的 useLayoutEffect 禁用后,恰好只有这一个测试失败。


🔴 新回归:20ce1e193 中把 requestAnimationFrame 换成了 setTimeout(…, 0)

-    const frame = requestAnimationFrame(() => {
+    const timer = window.setTimeout(() => {
       programmaticScrollRef.current = false;
       syncTooltip();
-    });
+    }, 0);

这个守卫的全部意义在于:我们自己滚动时间轴去把当前轮次居中时,不要把由此产生的 scroll 事件当作用户滚动而隐藏 tooltip。 而这只有在浏览器派发那个 scroll 事件时标志位仍为 true 才成立。

由命令式 scrollTop 赋值触发的 scroll 事件,是在 "update the rendering" 的 scroll steps 阶段派发的——它在定时器任务之后、在 requestAnimationFrame 回调之前。所以 rAF 永远安全,而 setTimeout(0) 是一场竞态。我在 Chromium 里直接测了(对 el.scrollTop = N 同时挂上三种回调,跑 5 次):

scroll -> rAF -> setTimeout0        (2/5)   两种写法下守卫都成立
setTimeout0 -> scroll -> rAF        (3/5)   scroll 到达时守卫已被清除  <-- bug

scroll5/5 次中都早于 rAFsetTimeout(0)3/5 次中早于 scroll。而当这次赋值是从渲染阶段的回调内部发起时,坏顺序是确定性发生的。

在真实组件上的端到端验证。 80 轮会话,用键盘焦点(focusin,完全不涉及鼠标)把 tooltip 固定在第 36 个 marker 上,然后在正文上滚 40 次滚轮。每一次都真实触发了时间轴重新居中,并且被聚焦的 marker 全程都留在时间轴 viewport 内——所以每一次隐藏都是虚假的:

构建 时间轴重新居中 虚假隐藏(锚点仍可见时 tooltip 却消失)
已提交 — setTimeout(0) 40/40 27 / 40
已提交 — setTimeout(0)(重跑) 40/40 32 / 40
已提交 — setTimeout(0)(A/B/A 复原后) 40/40 12 / 40
改回 requestAnimationFrame 40/40 0 / 40
改回 requestAnimationFrame(重跑) 40/40 0 / 40
改回 requestAnimationFrame(重跑) 40/40 0 / 40

rAF0/120setTimeout(0)71/120。已提交版本三次跑出 27/32/12 的离散度,正是竞态该有的样子。

具体场景:键盘用户 Tab 到某个时间轴 marker 去读它的 tooltip,而模型正在流式输出(follow 模式不断改变 currentIndex),于是每一次重新居中都会让 tooltip 闪掉,aria-describedby 也随之消失。鼠标 hover 同样会受影响——只要时间轴位移小于半个 marker 间距,指针仍停在同一个 marker 上,就不会有 mouseleave 来掩盖这个问题。

这个 PR 自己的测试就叫 keeps timeline details during current-turn centering …,所以这是对既定意图的回归,不是见仁见智的问题。

建议的修复——回退这一段。如果当初的动机是"后台标签页里 rAF 不会触发,导致标志位永远卡在 true",那么下面这个双保险写法能在保持正确顺序的前提下解决它:

const frame = requestAnimationFrame(() => {
  programmaticScrollRef.current = false;
  syncTooltip();
});
const fallback = window.setTimeout(() => {
  programmaticScrollRef.current = false;
}, 100);
return () => {
  cancelAnimationFrame(frame);
  window.clearTimeout(fallback);
  programmaticScrollRef.current = false;
};

⚠️ 为什么 CI 没抓到

keeps timeline details during current-turn centering but hides them on user scroll 这个测试在两种写法下都通过——我把 rAF 改回去后连跑了 4 次该文件:每次都是 42/42。jsdom 根本不会因为 scrollTop 赋值而派发 scroll 事件,所以测试只能手工派发一个,也就只能断言"标志位重置之后的 scroll 会隐藏 tooltip"。而被测的那个顺序,恰恰是它唯一观察不到的东西。因此同一提交里 nextFrame()nextTask() 的改动并不是因为真的失败了,只是把测试的等待时机重新对齐到新的原语上。

如果想覆盖这一点,断言必须是"由组件自己那次 scrollTop 赋值所产生的 scroll 事件不会隐藏 tooltip"——这需要真实浏览器,jsdom 做不到。


🔴 仍未解决:top: 50vh

MessageList.module.css 在这两个新提交中逐字节未变(git diff 38cc27834..14f2e75c6 -- …module.css → 0 行)。我还是在 14f2e75c6 上重新测了一遍,有头 Chromium,80 轮,宽 1440px——与前两轮完全一致:

视口 × 输入框 computed top 时间轴中心相对聊天中心的偏移 底部被裁掉 当前轮次 marker 可点击 可用 marker
900 × 132 450px +78 14 41/80
900 × 200 450px +112 82 37/80
900 × 260 450px +142 142 33/80
800 × 180 400px +102 82 33/80
700 × 132 350px +78 34 29/80
700 × 260 350px +142 162 21/80
 .sessionTimelineLayer {
   position: sticky;
-  top: 50vh;
+  top: 50%;

这已经是我第三轮提出这一点,既没有改动也没有回复。如果确实有必须用 50vh 的理由,请说明,我就不再坚持——但就实测数据而言,在每一种配置下 50% 都严格更优,而且这只是一个属性的改动。

@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for re-running this. The setTimeout(0) point is valid; I reproduced the ordering risk locally in Chromium as well. In 10 scrollTop-write probes, setTimeout(0) ran before the browser's scroll event in most runs, which means the programmatic scroll guard can indeed be cleared too early.

Fixed in 3a9e75b21:

  • restored the guard release to requestAnimationFrame, so the browser's own scroll event from our scrollTop write is still ignored;
  • kept a 100ms timeout fallback only to avoid leaving the guard stuck if rAF is paused;
  • moved the jsdom test wait back to nextFrame() to match the runtime primitive.

Verification on the committed tree:

  • npx prettier --check packages/web-shell/client/components/MessageList.tsx packages/web-shell/client/components/MessageList.dom.test.tsx packages/web-shell/client/components/MessageList.module.css => pass
  • npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx => 42 passed
  • npm run build --workspace=packages/web-shell => pass

On top: 50vh, this remains intentional and unchanged. The target placement is viewport/page visual centering rather than transcript-scrollport centering; the rail is independently scrollable for dense sessions, so I am not switching that property to 50% in this PR.

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Re-verification of 3a9e75b21

One new commit: keep timeline programmatic scroll guard through frame. Re-ran the local build and the real-browser A/B.

The scroll-guard regression is fixed, and I confirmed the fix is robust — including the failure mode I was worried about in my own suggestion. top: 50vh remains the only open item.

Gates: 1266 tests pass (79 files), MessageList.dom.test.tsx 42 pass, build passes, eslint 0 errors, 0 page errors.


✅ Scroll guard: fixed and stress-tested

Same driver as last round — 80-turn session, tooltip held open on a marker via keyboard focus, 40 wheel ticks over the transcript, every tick genuinely recentring the rail with the anchor staying visible:

Build Rail recentred Spurious hides
14f2e75c6setTimeout(0) 40/40 27 / 32 / 12 out of 40 (three runs)
3a9e75b21rAF + 100 ms fallback 40/40 0 / 0 / 0 (three runs)

I also checked the two things a guard like this can get wrong:

It doesn't get stuck. After a programmatic recentring the tooltip survives, and a subsequent genuine user scroll of the rail still hides it (survivesRecentring: true, userRailScrollStillHides: true). So the flag is cleared, not merely pinned.

The 100 ms fallback doesn't re-open the race. This was my worry about the shape I suggested: if the next frame lands more than 100 ms after the scrollTop write, the fallback would clear the flag before the scroll event arrives. I tested it directly — same schedule as the component (scrollTop = N, rAF, setTimeout(…, 100)), then a synchronous main-thread burn of 0 / 50 / 90 / 150 / 300 ms in the same task:

blockMs | observed order (5 runs each)
      0 | scroll -> rAF-clear -> fallback100    guard holds 5/5
     50 | scroll -> rAF-clear -> fallback100    guard holds 5/5
     90 | scroll -> rAF-clear -> fallback100    guard holds 5/5
    150 | scroll -> rAF-clear -> fallback100    guard holds 5/5
    300 | scroll -> rAF-clear -> fallback100    guard holds 5/5

scroll always lands first — 25/25. The reason is that Chromium dispatches the scroll event from an imperative write within 0.4–0.6 ms (I measured 16.6 ms once, at a frame boundary), so it is never anywhere near the 100 ms fallback even under heavy jank. My concern was unfounded; the fallback is pure insurance for the case where rAF never fires at all.

Everything from earlier rounds still holds on this head: tooltip portaled to document.body, position: fixed, z-index: 999, container color: rgb(250,250,250), 0 px clipping, role="tooltip" + aria-describedby, paints below a z-1000 modal backdrop, rail scrollbar hidden (scrollbar-width: none, ::-webkit-scrollbar { display: none }, gutter 0) while remaining scrollable, and marker 1 reachable by wheeling the rail with chat scroll delta 0.

⚠️ The fix is correct but still untested

I re-applied setTimeout(0) on top of this head and ran MessageList.dom.test.tsx three times: 42/42 every time. jsdom never dispatches a scroll event in response to a scrollTop write, so no jsdom test can distinguish the correct ordering from the broken one. The nextTask()nextFrame() revert restores symmetry with the implementation but adds no protection — a future relapse would land silently. Not a blocker, just worth knowing that this line is load-bearing and unguarded.


🔴 Still open: top: 50vh

No CSS changed in this commit. Re-measured on 3a9e75b21, headed Chromium, 80 turns, 1440 px wide — the same numbers for the fourth round, now shown next to what the one-line change gives:

viewport × composer computed top offset from chat centre rail bottom clipped current marker clickable usable → with top: 50%
900 × 132 450px +78 14 yes 41/80 off 0, clip 0, 41/80
900 × 200 450px +112 82 no 37/80 off 0, clip 0, 41/80
900 × 260 450px +142 142 no 33/80 off 0, clip 0, 41/80
800 × 180 400px +102 82 no 33/80 off 0, clip 0, 37/80
700 × 132 350px +78 34 no 29/80 off 0, clip 0, 30/80
700 × 260 350px +142 162 no 21/80 off 0, clip 20, 29/80

With top: 50% the current turn's marker is clickable in 6/6 configurations instead of 1/6, the rail sits exactly at the chat area's centre, and up to 8 more markers become reachable.

 .sessionTimelineLayer {
   position: sticky;
-  top: 50vh;
+  top: 50%;

This is the fourth round I've raised it. Everything else in this PR is now verified and good — this one property is all that stands between it and my approval. If you'd prefer, I'm happy to push the one-line change myself; just say the word.


中文版

3a9e75b21 的复验

新增一个提交:keep timeline programmatic scroll guard through frame。我重新跑了本地构建和真实浏览器 A/B。

滚动守卫的回归已修复,而且我验证了这个修复是稳健的——包括我自己当初提的方案里担心的那个失效路径。top: 50vh 是唯一剩下的未决项。

各项检查:1266 个测试通过(79 个文件),MessageList.dom.test.tsx 42 个通过,构建通过,eslint 0 错误,0 个页面报错。


✅ 滚动守卫:已修复并经过压力测试

与上一轮相同的驱动方式——80 轮会话,用键盘焦点把 tooltip 固定在某个 marker 上,在正文上滚 40 次滚轮,每一次都真实触发时间轴重新居中且锚点全程可见:

构建 时间轴重新居中 虚假隐藏
14f2e75c6setTimeout(0) 40/40 40 次里 27 / 32 / 12(三次运行)
3a9e75b21rAF + 100 ms 兜底 40/40 0 / 0 / 0(三次运行)

我还检查了这类守卫最容易出的两种问题:

它不会卡死。 程序化重新居中之后 tooltip 依然存在,而随后一次真正的用户滚动时间轴仍然会把它隐藏(survivesRecentring: trueuserRailScrollStillHides: true)。说明标志位是被清除了,而不是被钉死。

100 ms 的兜底不会重新打开竞态。 这正是我对自己提的那个写法的担忧:如果下一帧比 scrollTop 赋值晚了 100 ms 以上,兜底就会在 scroll 事件到达之前清除标志位。我直接测了——用与组件完全相同的调度(scrollTop = NrAFsetTimeout(…, 100)),然后在同一个任务里同步烧掉主线程 0 / 50 / 90 / 150 / 300 ms:

blockMs | 实际回调顺序(每档 5 次)
      0 | scroll -> rAF-clear -> fallback100    守卫成立 5/5
     50 | scroll -> rAF-clear -> fallback100    守卫成立 5/5
     90 | scroll -> rAF-clear -> fallback100    守卫成立 5/5
    150 | scroll -> rAF-clear -> fallback100    守卫成立 5/5
    300 | scroll -> rAF-clear -> fallback100    守卫成立 5/5

scroll 总是最先到达——25/25。原因是 Chromium 在命令式赋值后 0.4–0.6 ms 内就派发了 scroll 事件(我只在一次帧边界上测到 16.6 ms),因此即使在严重卡顿下也远远够不到 100 ms 的兜底。我的担忧不成立;这个兜底纯粹是给"rAF 完全不触发"那种情况买的保险。

前几轮确认的结论在这个 head 上依然成立:tooltip portal 到 document.bodyposition: fixedz-index: 999、容器 color: rgb(250,250,250)、裁剪 0 pxrole="tooltip" + aria-describedby、绘制在 z-1000 模态遮罩之下、时间轴滚动条隐藏(scrollbar-width: none::-webkit-scrollbar { display: none }、gutter 0)但仍可滚动,以及滚动时间轴滚轮可到达第 1 个 marker 且聊天滚动增量为 0。

⚠️ 修复是对的,但仍然没有测试保护

我在这个 head 上重新把 setTimeout(0) 打回去,跑了三次 MessageList.dom.test.tsx每次都是 42/42。jsdom 根本不会因为 scrollTop 赋值而派发 scroll 事件,所以任何 jsdom 测试都无法区分正确顺序和错误顺序。nextTask()nextFrame() 的回退让测试与实现重新对齐,但并没有带来任何保护——将来若再退回去,不会有任何报警。这不是阻塞项,只是想说明这一行是承重的、且无人看守。


🔴 仍未解决:top: 50vh

这个提交没有改任何 CSS。我还是在 3a9e75b21 上重新测了一遍,有头 Chromium,80 轮,宽 1440 px——第四轮,数据完全一致,这次把那一行改动的效果并排放出来:

视口 × 输入框 computed top 相对聊天中心的偏移 底部被裁掉 当前 marker 可点击 可用 → 改成 top: 50%
900 × 132 450px +78 14 41/80 偏移 0,裁剪 0,41/80
900 × 200 450px +112 82 37/80 偏移 0,裁剪 0,41/80
900 × 260 450px +142 142 33/80 偏移 0,裁剪 0,41/80
800 × 180 400px +102 82 33/80 偏移 0,裁剪 0,37/80
700 × 132 350px +78 34 29/80 偏移 0,裁剪 0,30/80
700 × 260 350px +142 162 21/80 偏移 0,裁剪 20,29/80

改成 top: 50% 后,当前轮次的 marker 在 6/6 种配置下都可点击(而不是 1/6),时间轴恰好落在聊天区域的正中心,并且最多多出 8 个可到达的 marker。

 .sessionTimelineLayer {
   position: sticky;
-  top: 50vh;
+  top: 50%;

这已经是我第四轮提出这一点了。这个 PR 的其余部分现在都已验证通过、状态良好——只剩这一个属性挡在合并之前。如果你愿意,我也可以直接帮你推这一行改动,说一声即可。

@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Local real-browser validation for 3a9e75b21

I built and ran a local browser-level harness for this PR head. It mounts the real web-shell MessageList timeline in a Vite page with the real app CSS chain, real dark/light theme variables, an 80-turn transcript, and a fixed composer footer. The visual checks run in Playwright Chromium layout/CSS, not jsdom; the only stub is the compact-mode provider so the harness can isolate this component without daemon/runtime services.

Validation results

  • Long-session timeline: 80 timeline entries rendered in both dark and light themes.
  • Internal rail scrolling: the rail scrolls from scrollTop=612 to 0 and back to the end without moving the transcript; in the normal composer case, the first and last markers are both reachable and hit-test clickable after rail scrolling.
  • Hidden scrollbar: Chromium computed scrollbar-width: none, ::-webkit-scrollbar { display: none }, and measured scrollbar gutter is 0px in both themes while the rail remains scrollable.
  • Tooltip layer: the detail tooltip is rendered under document.body, has role="tooltip", z-index: 999, uses the correct dark/light foreground color, is not clipped by the viewport, and disappears on user rail scroll.
  • Programmatic recenter guard: the tooltip survives the component's own current-turn rail recentering, confirming that the rAF guard still suppresses programmatic scroll events.
  • Local commands also passed: npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx reported 42/42 tests passing, and npm run build --workspace=packages/web-shell completed successfully.

On top: 50vh vs top: 50%

I agree with the reviewer measurement that top: 50% is better if the target is "center inside the transcript scrollport" and if the primary metric is initial current-marker hit testing with a tall composer. That is not the visual target I want for this PR.

The intended target is viewport/page visual centering. The composer is visible page chrome; when the timeline is centered only against the transcript body, it appears too high relative to the page the user is actually looking at. The timeline is deliberately its own scrollable control, so users can scroll the rail itself to select later/earlier markers outside the initially visible portion. This matches the intended Codex interaction model, and I do not consider the tall-composer overlap tradeoff a correctness bug that should block merge.

Measured in the local Chromium harness at 1440 x 900:

Composer height Current top: 50vh, center vs viewport Current top: 50vh, center vs chat body Initial current marker Forced top: 50%, center vs viewport Forced top: 50%, center vs chat body
132px +12px +78px hit-test clickable -66px 0px
200px +12px +112px initially covered by composer -100px 0px
260px +12px +142px initially covered by composer -130px 0px

So 50% does exactly what the reviewer says: it centers the rail in the chat body. The reason I am keeping 50vh is that it keeps the rail visually centered in the page/viewport instead of shifting it upward as the composer grows. Based on this validation, my merge recommendation is to keep top: 50vh; the remaining point is an intentional visual preference and interaction tradeoff, not an unaddressed implementation defect.

中文版

3a9e75b21 本地真实浏览器验证

我为这个 PR head 构造并运行了本地浏览器级验证 harness。它在 Vite 页面里挂载真实的 web-shell MessageList 时间轴,使用真实应用 CSS 链路、真实深色/浅色主题变量、80 轮会话记录,以及固定在底部的输入区。视觉验证运行在 Playwright Chromium 的真实布局/CSS 环境里,不依赖 jsdom;唯一的 stub 是 compact-mode provider,用来隔离组件,避免拉起 daemon/runtime 服务。

验证结果

  • 长会话时间轴:深色和浅色主题下都渲染了 80 条 timeline entry。
  • rail 内部滚动:rail 可以从 scrollTop=612 滚到 0 再滚回底部,并且不会带动正文 transcript 滚动;在普通输入区高度下,第一条和最后一条 marker 都能通过滚动 rail 到达并命中点击。
  • 隐藏滚动条:Chromium computed style 为 scrollbar-width: none::-webkit-scrollbar { display: none },实测 scrollbar gutter 是 0px,同时 rail 仍然可滚动。
  • Tooltip 浮层:详情 tooltip 渲染在 document.body 下,具备 role="tooltip"z-index: 999,深色/浅色主题文字颜色正确,没有被 viewport 裁剪,并且用户滚动 rail 后会消失。
  • 程序化重新居中守卫:组件自己为了当前轮次重新居中 rail 时,tooltip 不会被误隐藏,说明 rAF guard 仍然能压住程序化 scroll 事件。
  • 本地命令也通过:npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx 显示 42/42 通过,npm run build --workspace=packages/web-shell 成功完成。

关于 top: 50vhtop: 50%

我认同 reviewer 的测量:如果目标是“相对正文滚动区域居中”,并且主要指标是高输入框状态下当前 marker 初始可点,那么 top: 50% 更优。但这不是这个 PR 想要的视觉目标。

这里的目标是基于页面/视口的视觉居中。输入区本身也是用户正在看到的页面 chrome;如果只按正文区域居中,时间轴在用户视觉上会显得过于靠上。时间轴本身被设计成独立可滚动的控件,所以用户可以滚动 rail 自身来选择初始可视范围之外的前后 marker。这符合 Codex 这里的交互设计,我不认为高输入框下的重叠取舍是需要阻塞合并的正确性问题。

本地 Chromium harness 在 1440 x 900 下的测量:

输入区高度 当前 top: 50vh 相对 viewport 中心 当前 top: 50vh 相对正文中心 初始当前 marker 强制 top: 50% 相对 viewport 中心 强制 top: 50% 相对正文中心
132px +12px +78px 可命中点击 -66px 0px
200px +12px +112px 初始被输入区覆盖 -100px 0px
260px +12px +142px 初始被输入区覆盖 -130px 0px

所以 50% 的确实现了 reviewer 说的效果:时间轴相对正文区域居中。我保留 50vh 的原因是,它让时间轴在页面/视口中保持视觉居中,而不是随着输入区变高而在视觉上向上偏移。基于这轮验证,我建议合并时保留 top: 50vh;剩下这个点是有意的视觉偏好和交互取舍,不是尚未修复的实现缺陷。

Comment thread packages/web-shell/client/components/MessageList.tsx
@callmeYe
callmeYe dismissed stale reviews from qwen-code-ci-bot and qwen-code-ci-bot July 9, 2026 07:28

Resolved all review threads. The remaining rAF guard comment is a false positive against the current Chromium behavior and was answered with fresh Playwright real-browser evidence; no code change is needed.

@ytahdn ytahdn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. LGTM! ✅ Suggestion-level recommendations are in the Suggestion summary comment below.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Re-verification of 3a9e75b21 — one new finding, plus new evidence on top: 50vh

I rebuilt the harness from scratch this round rather than re-running the previous driver, and re-derived every number independently: the real MessageList mounted with its real providers and the real app CSS chain (.app.themeDark > .content > .list plus a real composer as a flex sibling of .content, since .chatSubtree is display: contents), driven in headless Chromium via Playwright. A/B is a file overlay of the two source files between merge-base 8296ce9e5 and this head.

Everything from rounds 1–4 still holds at 3a9e75b21. I found one new issue that none of the previous rounds covered: keyboard focus destroys the tooltip on any marker the rail has to scroll into view. And I have a new measurement that I think settles the top: 50vh discussion — not by repeating the same table, but by measuring the pixels rather than the box.


✅ Re-confirmed at this head

Check Result
MessageList.dom.test.tsx 42 / 42 pass
Revert-proof (PR's test file + merge-base source) 7 fail / 35 pass — the new tests are load-bearing
eslint --max-warnings 0 on the 3 changed files 0 errors
prettier --check on the 3 changed files clean
tsc -p packages/web-shell/tsconfig.json --noEmit 57 errors at this head, the identical 57 at merge-base → 0 new (all pre-existing, all in test files; web-shell has no typecheck script so CI never runs this)
First / last marker reachable 120-turn session, 1400×900, no composer. merge-base: marker 0 at y = −450.5, marker 119 at y = +1350.5elementFromPointnull, unclickable. This head: both hit-testable after scrolling the rail; clicking drives the transcript 20726 → 0 and 0 → 20726.
Rail scroll doesn't move the transcript 2000px over-wheel on the rail: rail 1212 → 0, transcript 20726 → 20726 (overscroll-behavior: contain)
Tooltip clipping Intersecting the tooltip rect against every ancestor with overflow != visible plus the window: merge-base 69.4% visible, this head 100%
Tooltip DOM 120 → 1 node; position: fixed, portalled to <body>, z-index: 999, role="tooltip", aria-describedby wired
Theme survives the portal dark color: rgb(250,250,250), light color: rgb(10,10,11); background/border resolve in both
Scheduled-task entries clock icon + data-scheduled-task="true" preserved inside the portal

🔴 New: keyboard focus destroys the tooltip on any marker that requires a rail scroll

handleViewportScroll dismisses the tooltip on any scroll not flagged as programmatic. But when you Tab onto a marker that sits outside the rail viewport, the browser scrolls it into view itself. That scroll is not covered by programmaticScrollRef, so onFocus shows the tooltip and the scroll event that the focus caused immediately hides it again.

Tab-walk across the rail, real Chromium, 120-turn session, 46 Tab presses:

Did focusing the marker require a rail scroll? markers tooltip shown
no 39 39 (100%)
yes 7 0 (0%)

aria-describedby drops to null on exactly those 7, so a screen reader loses the description precisely for the markers that weren't already visible. It bites the moment you Tab past the visible band — i.e. on exactly the long sessions this PR is about.

This is a regression against merge-base: there the details were plain CSS (:focus-visible .sessionTimelineDetails { opacity: 1 }), so focusing any reachable marker always revealed them (clipped, but present).

Why the suite can't see it. focusIn() in MessageList.dom.test.tsx only dispatches el.dispatchEvent(new FocusEvent('focusin')) — it never actually focuses the element, so document.activeElement is never the button — and jsdom has no focus scroll-into-view at all. Worse, the test hides timeline details when the user scrolls the timeline viewport does focusIn(button) and then dispatches a scroll, asserting the tooltip is gone. That is exactly the broken sequence, pinned as expected behaviour.

Fix I verified — restores 7 / 7, and all 42 existing tests still pass unchanged:

const handleViewportScroll = useCallback(() => {
  if (programmaticScrollRef.current) return;
  // A focus-driven scroll-into-view (Tab onto an off-screen marker) must
  // re-anchor the tooltip, not dismiss it.
  if (viewportRef.current?.querySelector('button:focus')) {
    syncTooltip();
    return;
  }
  hideTooltip();
}, [hideTooltip, syncTooltip]);

(handleViewportScroll has to move below syncTooltip's declaration.) syncTooltip already drops the tooltip when the anchor leaves the rail viewport, so this doesn't keep stale tooltips alive.

Worth naming the trade-off: this also means that if a rail button has focus and the user then wheel-scrolls the rail, the tooltip re-anchors instead of hiding. If you'd rather keep "any user scroll hides it", the equivalent fix is to raise programmaticScrollRef inside revealTooltip for one frame, so only the focus-induced scroll is swallowed. Either shape works; I verified the one above.

keyboard focus tooltip


top: 50vh — measuring the painted pixels rather than the box

You've declined this four times and I don't want to re-post the same table, so here is a different measurement that I think is the one that matters.

Your counter-table reports the panel box at a constant +12px from the viewport centre. That's correct. But .list is overflow-y: auto, and the rail lives inside it — so a growing share of that box is never painted. What the user sees is panel ∩ .list. Measuring that, at 1440×900, 80 turns:

composer painted rail never rendered painted centre Δ page centre (450) Δ chat centre current-turn marker
132px 143…768 15px 455 +5 +71 clickable
200px 143…700 83px 421 −28 +71 clipped
260px 143…640 143px 391 −58 +71 clipped
360px 143…540 243px 341 −108 +71 clipped

So the painted rail is off the page centre by −108 … +5px, and the error grows with the composer. It is not page-centred — and it isn't chat-centred either (a constant +71px). ChatEditor.module.css sets height: 140px at rest with --chat-editor-input-max-height: 300px, so the 200–360px rows are ordinary states, not corner cases.

I also measured the third placement, because it's the one that would actually deliver your stated goal. position: fixed; top: 50vh does escape .list's clip — painted 130…770, 0px lost, painted centre 450 = the page centre exactly, and 41/80 markers usable at every composer height. But then the rail paints on top of the composer: 7 markers at a 260px composer, 14 at 360px (verified by sampling elementFromPoint inside the composer's band, and by checking that rail pixels exist below its top edge — there are none in the 50vh and 50% arms).

placement trilemma

Which leaves three real options, not two:

placement fully painted? centred on? overlaps composer? usable markers (132 / 200 / 260px composer)
sticky; top: 50% yes chat area, exactly no 41 / 41 / 41
sticky; top: 50vh (this PR) no — up to 243px clipped neither no 41 / 37 / 33
fixed; top: 50vh yes page, exactly yes 41 / 41 / 41

A 640px rail that lives inside .list cannot be page-centred without either clipping it or drawing it over the composer. 50vh picks clipping, which is why the rail the user sees ends up centred on neither. If page-centring is genuinely the design you want, the change isn't the offset — it's getting the rail out of the scrollport (the tooltip already does exactly this, via createPortal + position: fixed).

Your call as author; I'm not going to keep re-litigating it. I just wanted the decision made against the painted geometry rather than the box geometry.

Related and independent: max-height: min(640px, max(220px, calc(100vh - 220px))) is also window-relative, so it assumes total chrome ≤ 220px.


One behaviour change worth stating explicitly

Capping the rail at 640px means ~41 markers are reachable without scrolling it. On merge-base the rail was 15N − 12px tall, so any session whose rail still fit the chat area (≈47 entries in a 702px-tall chat area, ≈60 in a full-height one) showed all of its markers at once. That's an intentional consequence of making the rail scrollable, and it's the right trade — but it isn't purely additive, and it isn't mentioned in the PR description.


Verdict

The core of this PR is correct and I've now confirmed it three different ways. The keyboard-focus item above is new, uncontested, and has a one-block fix that keeps every existing test green — I'd take that before merge. top: 50vh I'll leave with you, with the numbers.

中文版

3a9e75b21 复验 —— 一个新问题,以及关于 top: 50vh 的新证据

这一轮我没有复用之前的驱动脚本,而是从零重建了 harness 并独立重新测量了每一个数字:在无头 Chromium(Playwright)里挂载真实的 MessageList,配上真实的 provider 和真实的应用 CSS 链路(.app.themeDark > .content > .list,并把输入区做成 .content 的 flex 兄弟节点——因为 .chatSubtreedisplay: contents)。A/B 的做法是在 merge-base 8296ce9e5 与当前 head 之间覆盖那两个源文件。

第 1–4 轮的结论在 3a9e75b21 上依然成立。我发现了一个此前几轮都没有覆盖到的新问题:当 rail 需要滚动才能露出某个 marker 时,键盘 focus 会导致该 marker 的 tooltip 被销毁。另外,关于 top: 50vh,我有一个新的测量——不是重复同一张表,而是去测量"真正被绘制出来的像素"而非"盒子"。


✅ 在当前 head 上复验通过

检查项 结果
MessageList.dom.test.tsx 42 / 42 通过
反证(保留 PR 的测试文件 + 回退源文件到 merge-base) 7 失败 / 35 通过 —— 新测试确实有效
对 3 个改动文件跑 eslint --max-warnings 0 0 错误
对 3 个改动文件跑 prettier --check 通过
tsc -p packages/web-shell/tsconfig.json --noEmit 当前 head 57 个错误,merge-base 上是完全相同的 57 个 → 零新增(全部是既有问题,且都在测试文件里;web-shell 没有 typecheck script,所以 CI 从不跑它)
首 / 末 marker 可达 120 轮会话,1400×900,无输入区。merge-base:marker 0 在 y = −450.5,marker 119 在 y = +1350.5elementFromPoint 返回 null,无法点击。当前 head:滚动 rail 后两者都可命中;点击分别把正文从 20726 → 00 → 20726
滚动 rail 不会带动正文 在 rail 上过量滚动 2000px:rail 1212 → 0,正文 20726 → 20726overscroll-behavior: contain 生效)
Tooltip 裁剪 把 tooltip 矩形与所有 overflow != visible 的祖先以及窗口求交:merge-base 可见 69.4%,当前 head 100%
Tooltip DOM 节点数 120 → 1position: fixed,portal 到 <body>z-index: 999role="tooltip"aria-describedby 正确挂载
主题穿过 portal 深色 color: rgb(250,250,250),浅色 color: rgb(10,10,11);背景与边框在两种主题下都能解析
定时任务条目 时钟图标与 data-scheduled-task="true" 在 portal 里都保留

🔴 新问题:需要滚动 rail 才能露出的 marker,键盘 focus 时 tooltip 会被销毁

handleViewportScroll 会在任何"非程序化"的滚动时关掉 tooltip。但当你用 Tab 聚焦到一个位于 rail viewport 之外的 marker 时,浏览器会自己把它滚动进视野。这个滚动不在 programmaticScrollRef 的保护范围内,于是 onFocus 刚把 tooltip 显示出来,紧接着由 focus 引发的 scroll 事件又立刻把它隐藏了。

在 rail 上做 Tab 遍历(真实 Chromium,120 轮会话,按了 46 次 Tab):

聚焦该 marker 是否需要滚动 rail? marker 数 显示了 tooltip
39 39(100%)
7 0(0%)

这 7 个 marker 上 aria-describedby 同时变成 null,所以恰恰是那些原本就不可见的 marker,屏幕阅读器也读不到描述了。只要你 Tab 越过当前可见区间就会触发——也就是本 PR 所针对的长会话场景。

相对 merge-base 这是一处回归:在 merge-base 上详情是纯 CSS(:focus-visible .sessionTimelineDetails { opacity: 1 }),所以聚焦任何可达的 marker 都一定会显示详情(虽然会被裁剪,但确实显示)。

为什么测试抓不到。 MessageList.dom.test.tsx 里的 focusIn() 只是 el.dispatchEvent(new FocusEvent('focusin')),它从未真正聚焦元素,所以 document.activeElement 永远不是那个 button;而 jsdom 根本没有 focus 触发的 scroll-into-view。更麻烦的是,hides timeline details when the user scrolls the timeline viewport 这个测试正是先 focusIn(button) 再派发 scroll,然后断言 tooltip 消失——它把出问题的那条路径当成了预期行为固化下来。

我验证过的修复 —— 恢复到 7 / 7,且现有 42 个测试全部照常通过:

const handleViewportScroll = useCallback(() => {
  if (programmaticScrollRef.current) return;
  // 由 focus 引发的 scroll-into-view(Tab 到视野外的 marker)应当重新锚定
  // tooltip,而不是把它关掉。
  if (viewportRef.current?.querySelector('button:focus')) {
    syncTooltip();
    return;
  }
  hideTooltip();
}, [hideTooltip, syncTooltip]);

handleViewportScroll 需要移到 syncTooltip 声明之后。)syncTooltip 本身已经会在锚点离开 rail viewport 时丢弃 tooltip,所以这不会留下过期的 tooltip。

需要说明这里的取舍:这样改之后,如果某个 rail 按钮处于 focus 状态、用户再用滚轮滚动 rail,tooltip 会重新锚定而不是隐藏。如果你更希望保持"任何用户滚动都隐藏",等价的做法是在 revealTooltip 里把 programmaticScrollRef 置位一帧,只吞掉由 focus 引发的那次滚动。两种形态都可行;我验证的是上面这一种。


top: 50vh —— 测量"被绘制的像素"而不是"盒子"

这一点你已经拒绝了四次,我不想再贴同一张表,所以这里换一个我认为更关键的测量方式。

你的对照表给出的是面板盒子相对视口中心恒定 +12px。这没错。但 .listoverflow-y: auto,而 rail 就活在它里面——所以这个盒子里有越来越大的一部分根本不会被绘制。用户看到的是 panel ∩ .list。按这个口径测量(1440×900,80 轮):

输入区高度 实际绘制范围 未绘制 绘制部分的中心 Δ 页面中心 (450) Δ 正文中心 当前轮 marker
132px 143…768 15px 455 +5 +71 可点击
200px 143…700 83px 421 −28 +71 被裁剪
260px 143…640 143px 391 −58 +71 被裁剪
360px 143…540 243px 341 −108 +71 被裁剪

也就是说,实际绘制出来的 rail 相对页面中心偏移 −108 … +5px,而且输入区越高偏差越大。它并没有相对页面居中——同时也没有相对正文居中(恒定 +71px)。ChatEditor.module.css 里静息高度是 height: 140px--chat-editor-input-max-height: 300px,所以 200–360px 这几行是常规状态,不是边角情况。

我还测了第三种放置方式,因为它才是真正能实现你所说的目标的那个。position: fixed; top: 50vh 确实能逃出 .list 的裁剪——绘制范围 130…770,0px 丢失,绘制中心 450,恰好是页面中心,并且在任何输入区高度下都有 41/80 个 marker 可用。但代价是 rail 会画到输入区上面:260px 输入区时有 7 个 marker、360px 时有 14 个(用 elementFromPoint 在输入区范围内采样验证,并检查输入区上边缘以下是否存在 rail 像素——50vh50% 两种情况下都没有)。

所以真正的选项有三个,而不是两个:

放置方式 是否完整绘制 相对什么居中 是否压住输入区 可用 marker(输入区 132 / 200 / 260px)
sticky; top: 50% 精确相对正文区域 41 / 41 / 41
sticky; top: 50vh(本 PR) 否——最多裁掉 243px 都不是 41 / 37 / 33
fixed; top: 50vh 精确相对页面 41 / 41 / 41

一个 640px 高、又活在 .list 内部的 rail,不可能在既不被裁剪、又不压住输入区的前提下相对页面居中。50vh 选择了"被裁剪",这正是它最终既不相对页面居中、也不相对正文居中的原因。如果"相对页面居中"确实是你想要的设计,那要改的不是这个偏移量,而是把 rail 移出滚动容器(tooltip 已经正是这么做的:createPortal + position: fixed)。

这仍然由你作为作者来定;我不会继续在这一点上反复争论,只是希望这个决定是基于"绘制出来的几何"而不是"盒子的几何"来做的。

另有一个独立的相关点:max-height: min(640px, max(220px, calc(100vh - 220px))) 同样是相对窗口计算的,因此它隐含假设上下 chrome 总高不超过 220px。


一处值得写进 PR 描述的行为变化

把 rail 限高到 640px 意味着不滚动它时大约只有 41 个 marker 可达。而在 merge-base 上 rail 高度是 15N − 12 px,因此只要 rail 还能塞进正文区域(702px 高的正文区约 47 条,满高时约 60 条),全部 marker 都会一次性显示。这是让 rail 可滚动之后的有意取舍,方向也是对的——但它并非纯增量,PR 描述里也没有提到。


结论

这个 PR 的核心是正确的,我已经用三种不同方式确认过了。上面那个键盘 focus 的问题是新的、没有争议的,而且有一个不影响任何现有测试的小修复——建议合并前带上。top: 50vh 我把数据留给你判断。

@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the fresh re-verification. I treated the keyboard-focus finding as valid and fixed it in bb0d5ccb9.

What changed:

  • Focus now arms a short rail-scroll guard before showing the timeline tooltip. That preserves the tooltip and aria-describedby when Chromium scrolls an off-screen focused marker into the rail viewport.
  • The guard is intentionally temporary, so the existing behaviour still holds after focus settles: a user-initiated rail scroll hides the tooltip.
  • Added a DOM regression case for the focus-driven scroll sequence and kept the user-scroll dismissal case by waiting for the focus guard to expire first.

Verification on this branch:

  • npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx => 43 / 43 passed
  • npx prettier --check packages/web-shell/client/components/MessageList.tsx packages/web-shell/client/components/MessageList.dom.test.tsx packages/web-shell/client/components/MessageList.module.css => passed
  • node .qwen/e2e-tests/pr-6526-real-browser/run.mjs => passed, including a new Chromium probe where focusing u120 moved the rail 0 -> 1212, the tooltip stayed visible, aria-describedby stayed session-timeline-detail-tooltip, and a subsequent user wheel scroll hid the tooltip
  • npm run build --workspace=packages/web-shell => passed

On top: 50vh: I understand the painted-geometry trade-off you measured, but I am intentionally keeping the current placement. The desired product feel for this PR is page/viewport visual centering for the timeline rail, not transcript-body centering. Moving to sticky; top: 50% optimizes the chat-body center and makes the rail feel too high in the full page view. Moving the rail to fixed; top: 50vh would hit the page center more literally, but it can overlap the composer, which I do not want for this bugfix. So the current choice is deliberate: keep the rail visually centered against the page, keep it inside the message-list surface so it does not draw over the composer, and rely on the internal rail scroll for markers beyond the visible band.

The 640px cap / visible-marker subset is also an intentional result of making long timelines scrollable. The PR description already calls out that long marker lists now scroll internally; I do not think this needs another code change.

中文版

感谢这轮重新验证。我确认键盘 focus 的问题有效,并已在 bb0d5ccb9 修复。

改动内容:

  • marker 获得 focus 时,会先给 rail scroll 加一个短暂保护窗口,再显示 tooltip。这样 Chromium 为了把视野外 marker 滚入 rail viewport 而触发的滚动,不会立刻销毁 tooltip 和 aria-describedby
  • 这个保护窗口是临时的,所以 focus 稳定之后,用户主动滚动 rail 仍会隐藏 tooltip,保留原本交互语义。
  • 增加了 focus-driven scroll 的 DOM 回归测试,同时保留 user scroll 隐藏 tooltip 的测试,只是让它等 focus guard 结束后再触发滚动。

验证结果:

  • npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx => 43 / 43 通过
  • npx prettier --check packages/web-shell/client/components/MessageList.tsx packages/web-shell/client/components/MessageList.dom.test.tsx packages/web-shell/client/components/MessageList.module.css => 通过
  • node .qwen/e2e-tests/pr-6526-real-browser/run.mjs => 通过;新增 Chromium 探针中,focus u120 会让 rail 从 0 -> 1212,tooltip 仍可见,aria-describedby 仍为 session-timeline-detail-tooltip,随后用户滚轮滚动 rail 会隐藏 tooltip
  • npm run build --workspace=packages/web-shell => 通过

关于 top: 50vh:我理解你测到的“实际绘制像素”取舍,但这里仍然选择保留当前位置。本 PR 想要的是时间轴在页面/视口视觉上居中,而不是相对正文区域居中。改成 sticky; top: 50% 会优化正文中心,但在整页视角下会显得时间轴过于靠上。改成 fixed; top: 50vh 虽然能更字面地贴合页面中心,但会和输入区重叠,这不是这个 bugfix 想要的结果。因此当前取舍是有意的:时间轴保持页面视角上的视觉居中,同时仍留在消息列表区域内避免压到输入区;超出可视带的 marker 通过 rail 内部滚动访问。

640px 限高导致无需滚动时只能看到一部分 marker,也是长时间轴改成内部滚动后的预期结果。PR 描述已经说明长列表会内部滚动,我认为这里不需要再做代码修改。

@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

The web-shell E2E Smoke failure was a CI wiring issue exposed by this PR: the base workflow runs npm run test:e2e:smoke --workspace=packages/web-shell, but packages/web-shell did not define that script.

Fixed in f7480ee07 by adding the missing workspace script and pointing it at the focused web-shell smoke coverage we already have: client/App.test.tsx plus client/components/MessageList.dom.test.tsx.

Local verification:

  • npm run test:e2e:smoke --workspace=packages/web-shell => 93 / 93 passed
  • npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx => 43 / 43 passed
  • npx prettier --check packages/web-shell/package.json packages/web-shell/client/components/MessageList.tsx packages/web-shell/client/components/MessageList.dom.test.tsx packages/web-shell/client/components/MessageList.module.css => passed

@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Merge-conflict follow-up after syncing origin/main into this PR:

  • Resolved the only conflict in packages/web-shell/package.json by keeping the origin/main Web Shell script block.
  • That intentionally replaces the temporary Vitest-backed test:e2e:smoke fallback I added earlier with the real Playwright smoke command now present on main: playwright test --config playwright.config.ts --grep @smoke.
  • Pushed merge commit b835e03bd4393b529fe7f468dca229a074dc0154 normally; no force-push.

Local verification after the conflict resolution:

  • npm run test --workspace=packages/web-shell -- client/components/MessageList.dom.test.tsx -> 43 passed
  • npx prettier --check packages/web-shell/package.json packages/web-shell/client/components/MessageList.tsx packages/web-shell/client/components/MessageList.dom.test.tsx packages/web-shell/client/components/MessageList.module.css -> passed
  • npm run test:e2e:smoke --workspace=packages/web-shell -> 6 passed after installing the missing local Playwright Chromium cache
  • npm run build --workspace=packages/web-shell -> passed
中文说明

这次同步 origin/main 后只冲突在 packages/web-shell/package.json。我保留了 main 上新的 Web Shell 脚本集合,也就是使用真正的 Playwright smoke 命令替代之前为 CI 缺脚本临时补的 Vitest fallback。已通过普通 push 推送 merge commit b835e03bd4393b529fe7f468dca229a074dc0154

本地验证结果:MessageList DOM 测试 43 个通过,Prettier 检查通过,Web Shell Playwright smoke 6 个通过,Web Shell build 通过。

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Re-verification of b835e03bd — focus fix confirmed and stress-tested

Three new commits: bb0d5ccb9 (the focus-scroll guard), f7480ee07 (smoke script), b835e03bd (merge of main). Same harness as last round, re-run against the new head.

The keyboard-focus regression is fixed, and the new guard holds under every way I could think of to break it. top: 50vh is unchanged and re-measures byte-identically. The main merge brought in three things worth a look.


✅ The focus fix works

You took the second shape I suggested — arm a one-frame guard inside onFocus — rather than my button:focus check. That's the better choice: guardFocusScroll() runs in the onFocus handler, which the test helper focusIn() does reach, so the behaviour is actually testable in jsdom. A button:focus query never would have been.

Tab-walk across the rail, real Chromium, 120-turn session, 46 Tab presses:

Did focusing the marker require a rail scroll? markers 3a9e75b21 b835e03bd
no 39 39 39
yes 7 0 7

aria-describedby is wired on all 46, and the tooltip lands on its own marker to within ±0px (measured tooltip.style.top against the focused button's centre).

focus fix

Guard stress — 120 turns, 1400×900, 48px header + 150px composer. The risk with a guard like this is that it gets stuck, or swallows scrolls it shouldn't:

scenario expected observed
Tab onto an off-screen marker tooltip kept, correctly anchored ✅ present, Δanchor 0px
user wheels the rail immediately after hides — guard not stuck ✅ hidden
focus, wait 500ms (past rAF and the 100ms fallback), then wheel hides ✅ hidden
25× rapid Tab burst (key-repeat), then wheel hides — no stuck guard ✅ hidden
Shift+Tab backwards over the fold tooltip kept ✅ present, Δanchor 0px
mouse hover + wheel, no focus anywhere hides — pointer path unchanged ✅ hidden
chat scroll → programmatic recentring, focus held 0 spurious hides 0 / 25 (rail genuinely moved on 18/25 ticks), 0 mis-anchored

0 page errors (beyond my harness's own missing favicon).

The new test has teeth. Reverting only the MessageList.tsx hunk of bb0d5ccb9 fails exactly keeps timeline details when focus scrolls the timeline viewport — 1 failed / 42 passed — and nothing else.

Gates at b835e03bd:

Check Result
MessageList.dom.test.tsx 43 / 43 (via the package's own vitest.config.ts)
Revert-proof vs the new merge-base 637d00ceb 8 fail / 35 pass
eslint --max-warnings 0 0 errors
prettier --check on every file the PR touches clean
tsc -p tsconfig.json --noEmit 36 errors at head, the same 36 at merge-base → 0 new, 0 in MessageList.* (all in transcriptToMessages.ts etc., unbuilt-sibling noise in my worktree)
CI Test (ubuntu) pass, and the new web-shell E2E Smoke gate passes

🔴 Still open: top: 50vh

Unchanged, and it re-measures byte-for-byte identical to my last round (1440×900, 80 turns, computed top: 450px in every case):

composer painted rail never rendered Δ page centre current-turn marker
132px 143…768 15px +5 clickable
200px 143…700 83px −28 clipped
260px 143…640 143px −58 clipped
360px 143…540 243px −108 clipped

No new argument from me — I made the case last round and it's yours to call.


Three things the main merge brought in

1. b835e03bd drags 5 unrelated files into the diff. docs/users/configuration/model-providers.md, docs/users/features/commands.md, docs/users/features/computer-use.md, docs/users/qwen-serve.md, and packages/cli/src/ui/hooks/slashCommandProcessor.ts. I checked each one: it is byte-identical to prettier --write applied to main's content, so nothing was lost, and CI won't complain (the CI prettier step is --write, not --check). But it takes the PR from 3 files to 8, and the docs churn is pure table-pipe realignment. Worth dropping before merge.

2. f7480ee07 is now a no-op. Correction — you'd already explained this and I posted before reading it. I wrote this section before your 10:45 / 10:56 comments loaded on my side. For the record, I verified your account rather than take it on trust: at 3a9e75b21 the branch's packages/web-shell/package.json had no test:e2e:smoke key at all, while a pull_request run executes the merged workflow — i.e. main's ci.yml, which invokes exactly that script. So the smoke job really was failing on a CI-wiring gap that this PR exposed, f7480ee07 was the right temporary fix, and the merge deliberately superseded it with main's Playwright command. Nothing to do here; my "dead commit" framing was wrong.

3. The new web-shell E2E Smoke gate does not cover the timeline. client/e2e/web-shell.smoke.spec.ts contains no session-timeline selector. So the observation from last round stands unchanged: the layout half of this PR — top, max-height, the mask, the hidden scrollbar — is still invisible to CI, and it's exactly the half where the two real defects were. That spec is now the natural home for a guard, since it's a real browser.


🟡 Nit, new since #6572 landed in main

DialogShell.module.css now reads z-index: var(--web-shell-dialog-backdrop-z-index, 1000), while the tooltip hard-codes 999. Today nothing defines that variable, so 999 < 1000 still holds and my previous paint-order check remains valid. But the point of #6572 is to let an embedder override it — and an embedder that lowers the backdrop below 999 gets the timeline tooltip painting on top of the modal. Keeping them coupled would be:

z-index: calc(var(--web-shell-dialog-backdrop-z-index, 1000) - 1);

Not a blocker, and I did not render a dialog to prove it — this one is from reading the CSS.


Verdict

The focus fix is correct and robust; I tried hard to make the guard stick or swallow a real scroll and couldn't. Everything else re-confirmed at this head, CI green including the new browser gate. The only open item is top: 50vh.

中文版

b835e03bd 复验 —— focus 修复已确认,并做了压力测试

三个新提交:bb0d5ccb9(focus 滚动守卫)、f7480ee07(smoke 脚本)、b835e03bd(合并 main)。沿用上一轮的 harness,对新 head 重新跑了一遍。

键盘 focus 的回归已修复,新守卫在我能想到的各种破坏方式下都成立。top: 50vh 未改动,重新测量结果逐字节一致。合并 main 带进来三件值得看一眼的事情。


✅ focus 修复有效

你采用的是我给的第二种形态——在 onFocus 里置一帧守卫——而不是我的 button:focus 判断。这个选择更好:guardFocusScroll() 跑在 onFocus 处理函数里,而测试辅助函数 focusIn() 确实能触发它,因此这个行为在 jsdom 里是可测的;而 button:focus 查询在 jsdom 里永远看不到。

在 rail 上做 Tab 遍历(真实 Chromium,120 轮会话,按了 46 次 Tab):

聚焦该 marker 是否需要滚动 rail? marker 数 3a9e75b21 b835e03bd
39 39 39
7 0 7

46 个 marker 上 aria-describedby 全部正确挂载,且 tooltip 落在它自己的 marker 上,误差在 ±0px 以内(用 tooltip.style.top 与聚焦按钮的中心做比较)。

守卫压力测试 —— 120 轮,1400×900,48px header + 150px 输入区。这类守卫的风险在于:卡住不释放,或者吞掉了本不该吞的滚动:

场景 预期 实测
Tab 到视野外的 marker 保留 tooltip 且正确锚定 ✅ 存在,Δ锚点 0px
紧接着用户滚轮滚 rail 隐藏 —— 守卫没卡住 ✅ 隐藏
focus 后等 500ms(超过 rAF 100ms 兜底)再滚轮 隐藏 ✅ 隐藏
连按 25 次 Tab(模拟长按重复)后滚轮 隐藏 —— 无残留守卫 ✅ 隐藏
Shift+Tab 反向越过折叠处 保留 tooltip ✅ 存在,Δ锚点 0px
纯鼠标 hover + 滚轮(无 focus) 隐藏 —— 鼠标路径不受影响 ✅ 隐藏
滚动正文 → 程序化重新居中,focus 保持 0 次误隐藏 0 / 25(其中 18/25 次 rail 确实移动了),0 次错位

0 个页面错误(除了我 harness 自己缺 favicon 的 404)。

新测试是有效的。 只回退 bb0d5ccb9MessageList.tsx 的那段改动,失败的恰好是 keeps timeline details when focus scrolls the timeline viewport —— 1 失败 / 42 通过 —— 其余测试不受影响。

b835e03bd 上的各项门禁:

检查项 结果
MessageList.dom.test.tsx 43 / 43(用包自带的 vitest.config.ts 运行)
相对新 merge-base 637d00ceb 的反证 8 失败 / 35 通过
eslint --max-warnings 0 0 错误
对 PR 触及的所有文件跑 prettier --check 通过
tsc -p tsconfig.json --noEmit head 36 个错误,merge-base 同样 36 个 → 零新增MessageList.* 中 0 个(都在 transcriptToMessages.ts 等文件,是我 worktree 里 sibling 包未构建造成的噪音)
CI Test (ubuntu) 通过,新增的 web-shell E2E Smoke 门禁也通过

🔴 仍未解决:top: 50vh

未改动,重新测量与上一轮逐字节一致(1440×900,80 轮,各种情况下 computed top 均为 450px):

输入区高度 实际绘制范围 未绘制 Δ 页面中心 当前轮 marker
132px 143…768 15px +5 可点击
200px 143…700 83px −28 被裁剪
260px 143…640 143px −58 被裁剪
360px 143…540 243px −108 被裁剪

我不再补充新论据 —— 上一轮已经把理由讲完了,决定权在你。


合并 main 带进来的三件事

1. b835e03bd 把 5 个无关文件带进了 diff。 docs/users/configuration/model-providers.mddocs/users/features/commands.mddocs/users/features/computer-use.mddocs/users/qwen-serve.md,以及 packages/cli/src/ui/hooks/slashCommandProcessor.ts。我逐个核对过:它们与「对 main 的内容执行 prettier --write」的结果逐字节相同,所以没有丢失任何内容,CI 也不会报错(CI 的 prettier 步骤是 --write 而非 --check)。但这让 PR 从 3 个文件变成 8 个,而且文档改动纯粹是表格竖线的重新对齐。建议合并前剔除。

2. f7480ee07 现在是个空操作。 更正 —— 你其实已经解释过了,是我在读到之前就发了。 这一段是在你 10:45 / 10:56 两条评论出现之前写的。为严谨起见我没有直接采信,而是核实了:在 3a9e75b21 上,分支的 packages/web-shell/package.json 里根本没有 test:e2e:smoke 这个键,而 pull_request 事件跑的是合并后的 workflow,也就是 mainci.yml,它恰好调用了这个脚本。所以 smoke 任务确实是因为本 PR 暴露出的 CI 接线缺口而失败,f7480ee07 是当时正确的临时修复,合并时又有意用 main 的 Playwright 命令取代了它。这里无需处理;我"死代码"的说法是错的。

3. 新增的 web-shell E2E Smoke 门禁并不覆盖时间轴。 client/e2e/web-shell.smoke.spec.ts 里没有任何 session-timeline 选择器。所以上一轮的结论依旧成立:这个 PR 的布局部分(topmax-height、遮罩、隐藏滚动条)对 CI 仍然不可见,而恰恰是这一半藏着那两个真实缺陷。既然那个 spec 跑在真实浏览器里,它现在正是加防护的合适位置。


🟡 一个新出现的小问题(#6572 合入 main 之后)

DialogShell.module.css 现在写的是 z-index: var(--web-shell-dialog-backdrop-z-index, 1000),而 tooltip 里硬编码了 999。目前没有任何地方定义这个变量,所以 999 < 1000 依然成立,我之前的绘制顺序验证仍然有效。但 #6572 的意义正是让接入方可以覆盖它——一旦接入方把 backdrop 调到 999 以下,时间轴 tooltip 就会画在模态框上面。让两者保持耦合的写法是:

z-index: calc(var(--web-shell-dialog-backdrop-z-index, 1000) - 1);

这不是阻塞项,而且我没有真的渲染一个 dialog 去验证——这一条是读 CSS 得出的。


结论

focus 修复是正确且稳健的;我尽力去让守卫卡住或吞掉真实滚动,都没能做到。其余内容在这个 head 上全部复验通过,CI 全绿(包括新的浏览器门禁)。唯一未决的仍是 top: 50vh

@callmeYe

callmeYe commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the re-verification and for confirming the focus fix and CI state.

On top: 50vh, I want to close this out explicitly: this is an intentional product/design choice for this PR, not an unresolved correctness issue. The desired behavior is that the session timeline reads as visually centered in the page/viewport, while still staying inside the message-list surface so it does not draw over the composer. When there are more markers than fit in the visible rail band, the intended interaction is to use the rail's internal scroll to reach later or earlier markers.

I am not going to change this PR to top: 50%, and I am not going to move the rail to a fixed overlay to satisfy a stricter geometric page-center measurement. Those alternatives optimize different trade-offs that are not the desired UX here.

For future verification passes, please do not keep re-testing or re-reporting top: 50vh as an open item or blocker. It is an acknowledged visual preference and accepted trade-off, and I will not change it in this PR. Please focus any further testing on functional regressions, accessibility regressions, CI failures, or newly introduced issues.

中文说明

感谢复验,也感谢确认 focus 修复和 CI 状态。

关于 top: 50vh,这里明确收口:这是本 PR 有意选择的产品/视觉取向,不是一个尚未解决的正确性问题。期望效果是时间轴在页面/视口视角上看起来垂直居中,同时仍留在消息列表区域内,避免绘制到输入区上方。marker 数量超过可视 rail 区域时,预期交互就是通过 rail 自身滚动访问前后 marker。

我不会把这个 PR 改成 top: 50%,也不会为了更严格的几何页面居中测量把 rail 改成 fixed overlay。这两个方案是在优化不同的取舍,不是这里想要的 UX。

后续复验请不要继续把 top: 50vh 作为 open item 或 blocker 反复测试/报告。这个点已经是明确接受的视觉偏好和取舍,我不会在本 PR 中修改它。后续请把测试重点放在功能回归、可访问性回归、CI 失败或新引入的问题上。

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@ytahdn ytahdn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. LGTM! ✅

— qwen3.7-max via Qwen Code /review

@callmeYe
callmeYe added this pull request to the merge queue Jul 9, 2026
Merged via the queue into main with commit 0907edb Jul 9, 2026
33 of 34 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Read the diff independently against the problem statement ("long session timelines expose native scrollbar, clip tooltip details, earliest markers hard to select"). My independent proposal: wrap the <ol> in a fixed-height scrollable viewport, hide scrollbars cross-browser, move the tooltip to a portal to avoid clipping, programmatic-scroll the current marker into view, add edge fade.

The PR's implementation matches and exceeds this:

  • Viewport wrapper (sessionTimelineViewport): correctly wraps the <ol>, overflow-y: auto with max-height: min(640px, max(220px, calc(100vh - 220px))). Clean responsive bounds.
  • Scrollbar hiding: triple-layered — scrollbar-width: none (Firefox), ::-webkit-scrollbar { display: none } (Chrome/Safari), plus specificity-boosted double-selector .sessionTimelineViewport.sessionTimelineViewport::-webkit-scrollbar to ensure override. Solid cross-browser approach.
  • Edge fade mask: CSS mask-image with linear-gradient — transparent at edges, opaque in the middle. Nice touch.
  • Portal tooltip: createPortal to document.body with position: fixed — correctly escapes the scroll container's clipping. CSS variables forwarded from the panel via getComputedStyle to maintain theme consistency. role="tooltip" + aria-describedby for accessibility.
  • Focus-scroll guard: Arms a one-frame rAF guard + 100ms fallback timeout on focus. Prevents the browser's focus-scroll from dismissing the tooltip. Dual mechanism (rAF fast path + timeout safety net) is robust. All timers cleaned up on unmount.
  • Programmatic centering: Uses useLayoutEffect keyed on currentIndex. Sets programmaticScrollRef before scrollTop = ..., clears via rAF + fallback timeout. Clean.
  • Viewport clamping: Post-render pass that adjusts tooltip top to keep it within [12px, innerHeight - 12px]. Uses clamped flag to avoid infinite loops.

No critical blockers found. No AGENTS.md violations. The code is straightforward — no over-abstraction, no unnecessary helpers.

One cleanup item: the merge with main (b835e03bd) dragged in 5 unrelated files that are pure prettier formatting churn (4 docs files + slashCommandProcessor.ts). These should ideally be reverted before merge to keep the diff focused. Not a blocker.

Nit (non-blocking): tooltip z-index: 999 is hardcoded while DialogShell.module.css uses var(--web-shell-dialog-backdrop-z-index, 1000). If an embedder lowers the backdrop below 999, the tooltip paints on top of the modal. Coupling via calc(var(...) - 1) would be safer, but no rendering was done to confirm this is actually reachable.

Test Results

This is a web-shell React component — CLI tmux testing does not apply. Verification relied on unit tests, static analysis, and prior real-browser testing by the author and @wenshao.

Unit tests (PR branch, b835e03bd)

 RUN  v3.2.4 packages/web-shell/client

 ✓ components/MessageList.dom.test.tsx (43 tests) 1250ms

 Test Files  1 passed (1)
      Tests  43 passed (43)
   Duration  1.99s

43 tests include 7 new cases covering: scrollable long timelines (80 turns), portal-rendered tooltip with aria-describedby, viewport edge clamping, programmatic vs user scroll distinction, focus-out dismissal, and focus-scroll guard during keyboard navigation.

Static analysis

$ npx prettier --check MessageList.tsx MessageList.dom.test.tsx MessageList.module.css
All matched files use Prettier code style!

$ npx eslint --max-warnings 0 MessageList.tsx
(clean — 0 errors, 0 warnings)

$ npx tsc -p tsconfig.json --noEmit | grep MessageList
(0 errors in MessageList.* — 6 pre-existing errors in codeHighlighter.ts, unrelated)

CI

  • Test (ubuntu) → ✅ pass
  • web-shell E2E Smoke → ✅ pass (Playwright)
  • prettier → ✅ pass

Real-browser verification (from prior comments)

@wenshao independently verified in real Chromium with a 120-turn session:

  • 46 Tab presses: all aria-describedby wired correctly, tooltip anchor within ±0px
  • Focus-scroll guard stress tested: rapid Tab burst, Shift+Tab, wheel-after-focus, 500ms delay — all pass, 0 stuck guards
  • Revert-proof: reverting only the MessageList.tsx hunk of the focus fix fails exactly 1 test (keeps timeline details when focus scrolls the timeline viewport)
中文说明

代码审查

独立阅读 diff,对照问题描述("长时间轴暴露原生滚动条、裁剪详情 tooltip、最早 marker 难以选中")。我的独立方案:在 <ol> 外包一层固定高度的可滚动 viewport,跨浏览器隐藏滚动条,将 tooltip 移到 portal 避免裁剪,程序化滚动当前 marker 到可视区,添加边缘淡出。

PR 的实现匹配并超越了这一方案:

  • Viewport 包装器:正确包裹 <ol>overflow-y: auto 配合 max-height: min(640px, max(220px, calc(100vh - 220px))),响应式边界干净。
  • 滚动条隐藏:三层方案——scrollbar-width: none(Firefox)、::-webkit-scrollbar { display: none }(Chrome/Safari)、加上双选择器特异性增强。跨浏览器方案扎实。
  • 边缘淡出遮罩:CSS mask-image + linear-gradient——边缘透明,中间不透明。
  • Portal tooltipcreatePortaldocument.bodyposition: fixed——正确脱离滚动容器的裁剪。CSS 变量通过 getComputedStyle 从 panel 转发,保持主题一致性。role="tooltip" + aria-describedby 保证可访问性。
  • Focus-scroll 守卫:focus 时启用一帧 rAF 守卫 + 100ms 兜底超时。防止浏览器的 focus-scroll 销毁 tooltip。双机制(rAF 快路径 + timeout 安全网)稳健。所有定时器在卸载时清理。
  • 程序化居中useLayoutEffectcurrentIndex 为 key。设置 scrollTop 前置 programmaticScrollRef,通过 rAF + 兜底超时清除。
  • 视口钳位:渲染后调整 tooltip top,保持在 [12px, innerHeight - 12px] 范围内。用 clamped 标志避免无限循环。

无关键阻塞项。无 AGENTS.md 违规。代码直接——无过度抽象、无不必要的辅助函数。

一个清理项:main 的合并(b835e03bd)带入了 5 个无关文件,都是纯 prettier 格式化变动(4 个文档 + slashCommandProcessor.ts)。建议合并前回退以保持 diff 聚焦。非阻塞。

小问题(非阻塞): tooltip z-index: 999 硬编码,而 DialogShell.module.css 使用 var(--web-shell-dialog-backdrop-z-index, 1000)。如果接入方将 backdrop 调到 999 以下,tooltip 会画在模态框上面。用 calc(var(...) - 1) 耦合更安全,但未渲染验证。

测试结果

这是 web-shell React 组件——CLI tmux 测试不适用。验证依赖单元测试、静态分析,以及作者和 @wenshao 先前的真实浏览器测试。

单元测试(PR 分支,b835e03bd

 ✓ components/MessageList.dom.test.tsx (43 tests) 1250ms
 Test Files  1 passed (1)
      Tests  43 passed (43)

43 个测试包含 7 个新用例:可滚动长时间轴(80 轮)、portal 渲染 tooltip + aria-describedby、视口边缘钳位、程序化 vs 用户滚动区分、focus-out 隐藏、focus-scroll 守卫。

静态分析

  • prettier: 通过
  • eslint: 0 错误 0 警告
  • tsc: MessageList.* 中 0 错误(6 个预存错误在 codeHighlighter.ts,无关)

CI

  • Test (ubuntu) → ✅ 通过
  • web-shell E2E Smoke → ✅ 通过(Playwright)
  • prettier → ✅ 通过

真实浏览器验证(来自先前评论)

@wenshao 在真实 Chromium 中用 120 轮会话独立验证:46 次 Tab 按键全部 aria-describedby 正确挂载,tooltip 锚点误差 ±0px。Focus-scroll 守卫压力测试全部通过。回退验证:只回退 focus 修复的 TSX 部分恰好失败 1 个测试。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Verdict: Approve ✅

This PR does what it says — makes long session timelines scrollable and usable — and does it well. The implementation is direct: a viewport wrapper with hidden scrollbars and edge fade, a portal-rendered tooltip that escapes the scroll container, programmatic centering of the current marker, and a focus-scroll guard that handles the tricky case where keyboard navigation triggers browser auto-scroll.

Going in, my independent proposal for this problem was essentially what the PR implements, minus the resize sync and the dual-mechanism (rAF + timeout fallback) guard. The PR exceeds the baseline on those points.

The test suite has real teeth: 43 tests including 7 new ones that cover the specific failure modes (long timeline scrolling, portal tooltip, viewport clamping, focus-scroll guard). @wenshao independently verified the focus fix in real Chromium with a 120-turn session and couldn't break the guard under stress. Revert-proof confirms the new test catches exactly the right code.

CI is green across the board. Prettier, eslint, and tsc are all clean on the PR's files (6 pre-existing TS errors in codeHighlighter.ts are unrelated).

Two non-blocking items for the author to consider before merge:

  1. Drop the 5 formatting-only files from the merge commit (docs/users/configuration/model-providers.md, docs/users/features/commands.md, docs/users/features/computer-use.md, docs/users/qwen-serve.md, packages/cli/src/ui/hooks/slashCommandProcessor.ts). These are pure prettier table-pipe realignment from main and aren't part of this PR's work. Keeping the diff to the 3 actual files makes review and future git blame cleaner.

  2. Consider coupling the tooltip z-index to --web-shell-dialog-backdrop-z-index instead of hardcoding 999. Low-risk, easy to change later if needed.

Neither blocks the merge. The core fix is correct, well-tested, and ready to ship.

中文说明

结论:通过 ✅

这个 PR 做到了它承诺的——让长时间轴可滚动且可用——而且做得很好。实现方式直接:viewport 包装器隐藏滚动条并添加边缘淡出,portal 渲染的 tooltip 脱离滚动容器,程序化居中当前 marker,focus-scroll 守卫处理键盘导航触发浏览器自动滚动的棘手场景。

我的独立方案基本上就是 PR 实现的内容,但没有 resize 同步和双机制(rAF + timeout 兜底)守卫。PR 在这两点上超越了基线。

测试套件有实际覆盖:43 个测试包含 7 个新用例,覆盖特定失败模式(长时间轴滚动、portal tooltip、视口钳位、focus-scroll 守卫)。@wenshao 在真实 Chromium 中用 120 轮会话独立验证了 focus 修复,无法破坏守卫。回退验证确认新测试恰好捕获了正确的代码。

CI 全绿。Prettier、eslint、tsc 在 PR 文件上全部通过(6 个预存 TS 错误在 codeHighlighter.ts,无关)。

两个非阻塞建议:

  1. 从合并提交中移除 5 个纯格式化文件(4 个文档 + slashCommandProcessor.ts)。这些是来自 main 的 prettier 表格对齐,不属于本 PR 的工作。将 diff 保持为实际的 3 个文件使审查和未来的 git blame 更清晰。

  2. 考虑将 tooltip z-index 耦合到 --web-shell-dialog-backdrop-z-index 而不是硬编码 999。风险低,后续需要时容易修改。

两项都不阻塞合并。核心修复正确、测试充分、可以发布。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅ Two non-blocking suggestions in the review: drop the 5 formatting-only files from the merge commit, and consider coupling tooltip z-index to the dialog backdrop variable.

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.

4 participants