Prhub

#2794 dashboard: scroll the token strip instead of paging through it

原始 PR 作者 Shi-Dong 合并时间 2026-08-29 15:02 文件变更 3 提交数 2 评论 3 代码增减 +102 / -102

执行摘要

令牌条改为整条滚动,分页控件移除,请求减半

PR body 直接点明动机:长回复被切成 1024-token 一页,“clicking / through it page by page”,window / start 输入框是唯一能定位中间位置的方式;而图表本来就请求全量区间(start=0, end=total),窗口化令牌条等于重复请求服务器已在发送的字节。作者实测 64,006-token 样本:全量 /tokens 拉取约 920 ms(图表已支付),DOM 构建 66 ms、堆约 29 MB,“the windowing was never buying render time”。

值得精读,尤其是关心 dashboard 前端与超长列表渲染的工程师。作者先用实测证明 DOM 构建成本远低于直觉(64k span 仅 66 ms),再用 offsetParent + scrollTop 的懒定位替代分页状态,最后针对 review 发现的隐藏面板时序问题设计一次性 _onShown 回调;这三个决策都可迁移到其他长列表或懒加载面板场景。

讨论亮点

核心讨论围绕 claude[bot] 发现的时序问题:firstResponse.offsetTop 在 tokens 面板 detach 时为 0,加载完成后切回 Tokens 会永久停在 scrollTop 0。作者在第二个提交中把图表渲染与打开滚动移入一次性 _onShown 回调,用 root.getClientRects().length 判断面板是否已有布局,并由 select() 在取消隐藏后重新触发。claude[bot] 修复后确认 Nothing blocking 并 Approving;Zhichenzzz 评论认为 Claude Code review 有参考价值,随后也批准。

实现拆解

  1. 数据读取简化:在 miles/dashboard/static/views_tokens.jsloadTokensPane 中,用一次全量请求替代原 probe + windowed read 两次请求,删除 WINDOW_SIZESstartwindowSize 状态;available 统计提前到加载入口,stat 默认值改为从可用列表中选取。原因是图表本来就拉全量区间,窗口化只是重复请求同一批字节。
  2. 渲染全量一次构建paintStrip 根据 payload 生成全部 token span,掩码判断收敛到 isMasked helper,并让初次构建与 color by 重绘两处共用;切换指标时只重着色、不重建 DOM,因此滚动位置不再丢失。
  3. 滚动盒布局style.css.tokens 增加 max-height: 480pxoverflow-y: autoposition: relative,使 .tokens 成为其子 token 的 offsetParent;这样 token 的 offsetTop 就是把它滚到盒顶所需的 scrollTop,可以直接定位。
  4. 面板生命周期修正:两个 pane 始终挂载、用 hidden 切换,避免 detach 导致 scrollTop 重置;tokensPane._onShown 一次性回调负责“有布局后再滚动到首个生成 token 并渲染图表”,select() 取消隐藏后重新触发,这正是 review 提出的 offsetTop 时序问题的修复方案。
  5. 配套与验证README.md 更新 sample view 描述;仓库没有 JS 测试 harness,作者用 Playwright 驱动真实 Chrome 对 64,006-token 样本回归,覆盖 eval 视图、无 conversation sidecar、全 masked、10-token 等场景。
文件 模块 状态 重要度
miles/dashboard/static/views_tokens.js 样本视图 modified 8.51
miles/dashboard/static/style.css 样本视图 modified 3.09
miles/dashboard/README.md 样本视图 modified 1.94

关键符号

loadTokensPane renderTokens select paintStrip isMasked

关键源码片段

miles/dashboard/static/views_tokens.js core-logic

核心改动文件:移除分页状态,一次全量读取,新增 `isMasked` / `paintStrip` / `_onShown` 逻辑,token 条从窗口化改为整条滚动。

// 关键实现:令牌条从“分页窗口”改为“整条滚动”,并处理好隐藏面板的布局时机。
// 注释按 PR 描述整理,省略无关分支。// 掩码判定:loss 掩码非 0 的位置在绘制时调暗,且不参与按指标着色
function isMasked(payload, index) {
  const mask = payload.loss_mask?.[index];
  return mask != null && mask !== 0; // 0 表示无掩码
}// 在 loadTokensPane 内部:创建滚动盒并一次性构建全部 span
const strip = el('div', { class: 'tokens' });
const firstResponse = paintStrip(strip, payload, stat); // 返回首个生成 token 的 span// 两个 pane 始终挂载、用 hidden 切换:一旦把 tokensPane 从文档摘除,
// 浏览器会丢弃它的布局并把 scrollTop 重置为 0
const select = (name) => {
  conversationPane.hidden = name !== 'conversation';
  tokensPane.hidden = name !== 'tokens';
  if (name === 'tokens') {
    startTokens(); // 懒加载只执行一次
    tokensPane._onShown?.(); // 补偿面板隐藏期间完成的延迟加载
  }
};// 打开时直接滚到第一个生成 token;agentic prompt 可能长达数千 token,
// 从位置 0 出发会让读者离值得看的内容太远
tokensPane._onShown = () => {
  // hidden 状态下 getClientRects() 为空且 offsetTop 恒为 0,
  // 此刻什么都不做,等 select() 取消隐藏后再触发一次
  if (!tokensPane.getClientRects().length || tokensPane._scrollApplied) return;
  tokensPane._scrollApplied = true; // 一次性:后续切标签不再重滚
  strip.scrollTop = firstResponse.offsetTop; // .tokens 是 offsetParent
};

评论区精华

自动滚动到首个生成 token 依赖 offsetTop,隐藏面板时失效 正确性

claude[bot] 指出:自动滚动依赖 `firstResponse.offsetTop`,但用户若在 fetch 等待时切到 Conversation 标签,tokensPane 会从文档 detach,此时 `offsetTop` 为 0;加载完成后切回 Tokens,整条序列显示但永久停在 `scrollTop` 0。

结论:第二个 commit(96efc)把滚动与图表渲染移入一次性 `_onShown` 回调,先用 `root.getClientRects().length` 判断面板是否已有布局,再由 `select()` 在取消隐藏后重新触发;claude[bot] 后续确认修复有效并 Approving。 · 已解决

风险与影响

  • DOM 规模:每个 token 一个 span,64k 样本约 29 MB 堆;几十万 token 的超长样本可能推高内存与样式重算开销。
  • 布局时序:滚动定位依赖 offsetTop,hidden 场景已由 _onShown 修复;未来若绕过 select() 直接挂载 tokensPane,回调可能不触发。
  • 测试覆盖:仓库无 JS 测试 harness(无 package.json),本次验证靠 Playwright 手工回归,后续回归缺少自动防线。
  • 事件性能:tooltip 仍为 per-span 监听,mousemove 在 64k span 上约 66 ms;作者评估委托方案只省 6 ms,不值得承担回归风险,但超长样本仍可能卡顿。
  • 用户体验:长回复不再需要逐页点击,滚动条直达任意位置;颜色标尺按全量计算后,同一值在不同区域颜色一致;切换指标不丢滚动位置。
  • 系统负载:每个样本面板的 /tokens 调用从 3 次降为 1 次,减少服务端重复计算与传输;代价是浏览器内存增加(典型 64k 样本约 29 MB)。
  • 团队维护:静态前端改动,无 API/schema 变化,Python 测试不受影响;后续改前端行为需维持“面板常驻 + 布局时机回调”的约束。
缺少前端自动化测试 DOM 内存随 token 数线性增长 滚动定位依赖布局时序 保留 per-span 事件监听

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论