From 21d039be1b95305cca9e4de10d0b8fd7eac69623 Mon Sep 17 00:00:00 2001 From: Yif <877193178@qq.com> Date: Fri, 28 Aug 2026 15:03:47 +0800 Subject: [PATCH] fix(web): label the Turn-time TTFT row as first-token latency, not an average The Turn fold publishes the first step's TTFT (contract/turn-metrics.ts firstStepTtftMs), never an average; only the Session StatsLine averages across steps. Rename the dialog row in both locales and align the Agent Note with what actually ships. --- .../feature/2026-08-28-web-turn-stat-pills.i18n.yaml | 4 ++-- .../implemented/feature/2026-08-28-web-turn-stat-pills.md | 2 +- .../implemented/feature/2026-08-28-web-turn-stat-pills.zh.md | 2 +- apps/web/tests/turn-tail-actions.e2e.ts | 2 +- packages/client/ui-chat/src/client/locale.ts | 4 ++-- packages/client/ui-chat/tests/chat-view.client.spec.tsx | 2 +- .../client/ui-chat/tests/turn-usage-panel.client.spec.tsx | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.i18n.yaml b/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.i18n.yaml index a044b62071..8085d4a54a 100644 --- a/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.md -2026-08-28-web-turn-stat-pills.md: cfba7db77507f88bdf1f03b80d62751830e5122d -2026-08-28-web-turn-stat-pills.zh.md: 05e5f193f246b3245c2ce169547950326fb7557f +2026-08-28-web-turn-stat-pills.md: 2661c228b9a5a7b55ba3698cf3ee2b4c76701bbc +2026-08-28-web-turn-stat-pills.zh.md: 5cc889b9f743eefeac1343b70743c23392efc908 diff --git a/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.md b/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.md index cfba7db775..2661c228b9 100644 --- a/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.md +++ b/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.md @@ -10,7 +10,7 @@ A completed assistant Turn ended with two stacked footer rows: a `Turn usage` Di ## Decision -The tail keeps one `MessageIconActions` row. Two stat pills sit right of the branch action: a database pill labelled with the compact Turn total (`Usage 15.8K tok`) and a clock pill labelled with the wall time (`Ran for 19s`); the message clock stays plain text at the row end. Each pill is an `aria-haspopup="dialog"` trigger that portals a fixed-position dialog to `document.body`, placed above the trigger by `useAnchoredPosition` with a 12px viewport clamp and closed by outside pointerdown or Escape (ContextMeter's pattern). The usage dialog holds the exact total, provider/model routes, cache-hit rate, token buckets, and the reasoning subset inline in Output; the time dialog holds total run time, decode TPS, and average TTFT. Facts absent from the fold render no row, and a window without publishable Turn usage renders no usage pill; the token-meter fold and `turn/start` gating are unchanged from [exact per-Turn usage](2026-08-24-web-per-turn-token-usage.md). +The tail keeps one `MessageIconActions` row. Two stat pills sit right of the branch action: a database pill labelled with the compact Turn total (`Usage 15.8K tok`) and a clock pill labelled with the wall time (`Ran for 19s`); the message clock stays plain text at the row end. Each pill is an `aria-haspopup="dialog"` trigger that portals a fixed-position dialog to `document.body`, placed above the trigger by `useAnchoredPosition` with a 12px viewport clamp and closed by outside pointerdown or Escape (ContextMeter's pattern). The usage dialog holds the exact total, provider/model routes, cache-hit rate, token buckets, and the reasoning subset inline in Output; the time dialog holds total run time, decode TPS, and the Turn's first-token latency (the first step's TTFT). Facts absent from the fold render no row, and a window without publishable Turn usage renders no usage pill; the token-meter fold and `turn/start` gating are unchanged from [exact per-Turn usage](2026-08-24-web-per-turn-token-usage.md). Row visibility follows recency: turn tails and user rows tag `data-actions-reveal`, the latest of each kind stays `always` visible, earlier rows reveal on hover or focus-within under `@media (hover: hover)`, and no-hover devices keep every row visible. Below 480px the pill labels hide and each pill takes the sibling action-button geometry — 28px width, 6px padding, centered glyph, and no adjacent-pill margin rebate — so the bare icons keep the row's 8px rhythm. diff --git a/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.zh.md b/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.zh.md index 05e5f193f2..5cc889b9f7 100644 --- a/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.zh.md +++ b/.agents/notes/implemented/feature/2026-08-28-web-turn-stat-pills.zh.md @@ -10,7 +10,7 @@ ## 决定 -尾部只保留一行 `MessageIconActions`。分叉操作右侧放两个统计 pill:数据库图标 pill 标注紧凑的本轮总量(`用量 15.8K tok`),时钟图标 pill 标注墙钟用时(`用时 19秒`);消息时钟保持纯文字置于行尾。每个 pill 是 `aria-haspopup="dialog"` 触发器,把固定定位的弹层 portal 到 `document.body`,由 `useAnchoredPosition` 锚定在触发器上方并保持 12px 视口边距,外部 pointerdown 或 Escape 关闭(沿用 ContextMeter 模式)。用量弹层承载精确总量、提供方/模型路由、缓存命中率、token 分桶及输出内联的推理子集;用时弹层承载本轮总用时、解码 TPS、首 token 平均用时。fold 未产出的事实不渲染行,窗口内无可发布的 Turn 用量则不渲染用量 pill;token-meter fold 与 `turn/start` 门控沿用[精确 per-Turn 用量](2026-08-24-web-per-turn-token-usage.zh.md),未做改动。 +尾部只保留一行 `MessageIconActions`。分叉操作右侧放两个统计 pill:数据库图标 pill 标注紧凑的本轮总量(`用量 15.8K tok`),时钟图标 pill 标注墙钟用时(`用时 19秒`);消息时钟保持纯文字置于行尾。每个 pill 是 `aria-haspopup="dialog"` 触发器,把固定定位的弹层 portal 到 `document.body`,由 `useAnchoredPosition` 锚定在触发器上方并保持 12px 视口边距,外部 pointerdown 或 Escape 关闭(沿用 ContextMeter 模式)。用量弹层承载精确总量、提供方/模型路由、缓存命中率、token 分桶及输出内联的推理子集;用时弹层承载本轮总用时、解码 TPS、本轮首 token 用时(取首个 step 的 TTFT)。fold 未产出的事实不渲染行,窗口内无可发布的 Turn 用量则不渲染用量 pill;token-meter fold 与 `turn/start` 门控沿用[精确 per-Turn 用量](2026-08-24-web-per-turn-token-usage.zh.md),未做改动。 行可见性按新近度门控:turn 尾行与用户行标记 `data-actions-reveal`,各自最新一行保持 `always` 常显,更早的行在 `@media (hover: hover)` 下 hover 或 focus-within 才显示,无 hover 设备恒显示。480px 以下 pill 隐藏文字并取同排操作按钮的几何——28px 宽、6px 内边距、图标居中、取消相邻 pill 的边距补偿——让裸图标保持行的 8px 节奏。 diff --git a/apps/web/tests/turn-tail-actions.e2e.ts b/apps/web/tests/turn-tail-actions.e2e.ts index 40f5f0e017..5b5520f93d 100644 --- a/apps/web/tests/turn-tail-actions.e2e.ts +++ b/apps/web/tests/turn-tail-actions.e2e.ts @@ -198,7 +198,7 @@ describe('web e2e: assistant IconActions wait for the turn to end', () => { const timeDialog = page.getByRole('dialog', { name: 'Turn time and speed' }) expect(await timeDialog.count()).toBe(1) expect(await timeDialog.getByText(/tok\/s/).count()).toBe(1) - expect(await timeDialog.getByText('Average time to first token (TTFT)', { exact: true }).count()).toBe(1) + expect(await timeDialog.getByText('Time to first token (TTFT)', { exact: true }).count()).toBe(1) await page.keyboard.press('Escape') await trigger.click() diff --git a/packages/client/ui-chat/src/client/locale.ts b/packages/client/ui-chat/src/client/locale.ts index 82a3329d05..cd0f6f50ce 100644 --- a/packages/client/ui-chat/src/client/locale.ts +++ b/packages/client/ui-chat/src/client/locale.ts @@ -102,7 +102,7 @@ export const zh = { 'message.turnTime.title': '本轮用时和速度', 'message.turnTime.duration': '本轮总用时', 'message.turnTime.speed': '输出速度(TPS)', - 'message.turnTime.ttft': '首 token 平均用时(TTFT)', + 'message.turnTime.ttft': '首 token 用时(TTFT)', 'duration.seconds': '{seconds}秒', 'duration.minutes': '{minutes}分{seconds}秒', 'command.running': '执行中…', @@ -218,7 +218,7 @@ export const en = { 'message.turnTime.title': 'Turn time and speed', 'message.turnTime.duration': 'Total run time', 'message.turnTime.speed': 'Tokens per second (TPS)', - 'message.turnTime.ttft': 'Average time to first token (TTFT)', + 'message.turnTime.ttft': 'Time to first token (TTFT)', 'duration.seconds': '{seconds}s', 'duration.minutes': '{minutes}m {seconds}s', 'command.running': 'Running…', diff --git a/packages/client/ui-chat/tests/chat-view.client.spec.tsx b/packages/client/ui-chat/tests/chat-view.client.spec.tsx index 6b72d28236..bc3022cc3d 100644 --- a/packages/client/ui-chat/tests/chat-view.client.spec.tsx +++ b/packages/client/ui-chat/tests/chat-view.client.spec.tsx @@ -1583,7 +1583,7 @@ describe('ChatView', () => { expect(timeDialog.getAttribute('aria-label')).toBe('本轮用时和速度') expect(timeDialog.textContent).toContain('本轮总用时19秒') expect(timeDialog.textContent).toContain('输出速度(TPS)20 tok/s') - expect(timeDialog.textContent).toContain('首 token 平均用时(TTFT)1.2秒') + expect(timeDialog.textContent).toContain('首 token 用时(TTFT)1.2秒') }) it('withholds the usage-details trigger when turn usage is outside the window', () => { diff --git a/packages/client/ui-chat/tests/turn-usage-panel.client.spec.tsx b/packages/client/ui-chat/tests/turn-usage-panel.client.spec.tsx index c9ef6d45f1..cd1704eb61 100644 --- a/packages/client/ui-chat/tests/turn-usage-panel.client.spec.tsx +++ b/packages/client/ui-chat/tests/turn-usage-panel.client.spec.tsx @@ -116,7 +116,7 @@ describe('TurnTimePanel', () => { const details = dialog.querySelector('[data-turn-time-details]') as HTMLElement expect(details.textContent).toContain('Total run time19s') expect(details.textContent).toContain('Tokens per second (TPS)20 tok/s') - expect(details.textContent).toContain('Average time to first token (TTFT)1.2s') + expect(details.textContent).toContain('Time to first token (TTFT)1.2s') fireEvent.keyDown(document, { key: 'Escape' }) expect(view.queryByRole('dialog')).toBeNull()