perf(chat): avoid ancestor invalidation in history spacing
This commit is contained in:
1 parent
e7436013d1
commit
f29deda51f
7 files changed
+42
-6
No files matched your search
@@ -26,6 +26,9 @@
|
||||
/agent-note-frontend-large-session-performance-budgets/calibration/trajectory-readiness-polling:
|
||||
en: bdcfefd7358e335b
|
||||
zh: f718f66ca5944203
|
||||
/agent-note-frontend-large-session-performance-budgets/calibration/history-selector-invalidation:
|
||||
en: 2d2f78f4cf6aad8a
|
||||
zh: aec6af5f7c66de16
|
||||
/agent-note-frontend-large-session-performance-budgets/alternatives-considered:
|
||||
en: 4d2a4a64ccd14507
|
||||
zh: 8a4aaf4d4abea13d
|
||||
|
||||
@@ -86,6 +86,22 @@ A controlled macOS arm64 diagnostic on Node 24.19.0, Chromium 149.0.7827.55 and
|
||||
|
||||
Complete local 240-turn runs produce Trajectory samples of 157.795/146.737/148.571 ms before the observer change and 130.184/191.247/157.543 ms afterward, with medians 148.571 and 157.543 ms. Both runs pass every fixed endpoint budget; no local speedup or hosted calibration is claimed. The readiness regression rejects a hidden searchbox or record despite a visible unrelated table row; restoring the original selector waits makes that test fail.
|
||||
|
||||
### History selector invalidation
|
||||
|
||||
The older-history control is the immediate sibling before the `display: contents` flow anchor. Leading spacing therefore uses `.column > .older + [data-slot="conversation.chat.flow"]`, preserving the earlier selector's specificity and visible-row filtering. An ancestor `.column:has(> .older)` combined with the descendant empty-row `:has()` makes unrelated draft mutations invalidate the history subtree. A temporary Chromium invalidation trace identified this exact spacing selector and style recalculations covering 13,591–13,641 elements. The trace is causal evidence, not a latency measurement: tracing itself adds substantial overhead.
|
||||
|
||||
On Linux x64, Node 24.19.0 and Chromium 149.0.7827.55, separate three-sample runs of the unchanged browser benchmark measured these medians using the original and sibling selectors. Both retained all 25,409 DOM nodes and every trusted input overlapped the streaming reply.
|
||||
|
||||
| Endpoint | Original selector | Sibling selector |
|
||||
|---|---:|---:|
|
||||
| Draft typing | 517.39 ms | 378.50 ms |
|
||||
| First reply | 1556.89 ms | 801.58 ms |
|
||||
| Stream main-thread task | 2540.59 ms | 1774.75 ms |
|
||||
|
||||
These local runs both failed the independent open budget: their open medians were 1192.85 and 1666.99 ms. A separate six-sample diagnostic alternated the original and sibling selectors in the browser CSSOM before timing, using the same built artifacts. Its original/sibling open wall medians were 2598.99/1349.22 ms, while Chromium task medians were 325.67/305.90 ms. This finds no browser-computation regression in those samples, but does not explain the remaining wait or certify the whole benchmark. The alternating diagnostic measured typing medians of 350.18/362.82 ms, so the earlier typing improvement is not uniform across runs; first-reply medians were 1498.48/815.85 ms and stream-task medians 2449.69/1915.73 ms. The diagnostic changes are not shipped; workload, overlap assertions, budgets and benchmark source remain unchanged.
|
||||
|
||||
Post-GC browser heap was 80.70/80.71 MiB. GNU time reported maximum RSS of 535.78/561.80 MiB for each benchmark command and its children; this is not input-phase memory or an aggregate browser-memory measurement. Moving the composer's selection reveal into an animation frame was rejected: profiling showed the style flush moved to another geometry read, without improving typing. The product fix removes the invalidation source instead.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Use the Node fold as paint evidence.** Rejected because it never performs DOM mutation, layout, or browser scheduling. The focused reconnect case likewise makes no GUI speed claim.
|
||||
|
||||
@@ -86,6 +86,22 @@ Trajectory 激活在动画帧上采样可见搜索框与 `[data-trajectory-scrol
|
||||
|
||||
完整本地 240 轮运行在修改观察器前得到 Trajectory 样本 157.795/146.737/148.571 ms,修改后为 130.184/191.247/157.543 ms,中位数分别为 148.571 和 157.543 ms。两次运行均通过所有固定终点预算;不声称本地提速或托管环境校准。即使存在可见的无关表格行,就绪回归测试仍拒绝隐藏的搜索框或记录;恢复原选择器等待会使该测试失败。
|
||||
|
||||
### 历史区域选择器失效
|
||||
|
||||
更早历史控件紧邻 `display: contents` 流锚点之前。因此首行间距使用 `.column > .older + [data-slot="conversation.chat.flow"]`,保持原选择器的优先级与可见行过滤。祖先 `.column:has(> .older)` 与后代空行 `:has()` 组合,会让无关的草稿修改使历史子树样式失效。临时 Chromium 失效跟踪定位到这条具体的间距选择器,样式重算覆盖 13,591–13,641 个元素。跟踪用于定位原因,不用于测量延迟:跟踪本身会增加大量开销。
|
||||
|
||||
在 Linux x64、Node 24.19.0 与 Chromium 149.0.7827.55 上,使用原选择器与兄弟选择器分别执行未修改的浏览器基准,每组取三个样本的中位数。两组都保留全部 25,409 个 DOM 节点,且每个受信任的输入都与流式回复重叠。
|
||||
|
||||
| 终点 | 原选择器 | 兄弟选择器 |
|
||||
|---|---:|---:|
|
||||
| 草稿键入 | 517.39 ms | 378.50 ms |
|
||||
| 首段回复 | 1556.89 ms | 801.58 ms |
|
||||
| 流式主线程任务 | 2540.59 ms | 1774.75 ms |
|
||||
|
||||
这两次本地运行都未通过独立的打开预算:打开中位数分别为 1192.85 与 1666.99 ms。另一项六样本诊断使用同一份构建产物,在计时前通过浏览器 CSSOM 交替使用原选择器与兄弟选择器。原版/修改版的打开壁钟中位数为 2598.99/1349.22 ms,Chromium 任务中位数为 325.67/305.90 ms。这些样本未观察到浏览器计算回归,但没有解释剩余等待,也不代表完整基准已通过。交替诊断的键入中位数为 350.18/362.82 ms,因此前述键入改善并非每次运行都能观察到;首段回复中位数为 1498.48/815.85 ms,流式任务中位数为 2449.69/1915.73 ms。诊断改动不随产品提交;负载、重叠断言、预算与基准源码保持不变。
|
||||
|
||||
强制 GC 后的浏览器 heap 为 80.70/80.71 MiB。GNU time 为两次基准命令及其子进程报告的最大 RSS 为 535.78/561.80 MiB;这不是输入阶段内存,也不是浏览器总内存测量。曾尝试将输入框的选区显示推迟到动画帧,但分析显示样式刷新转移到另一处几何读取,没有改善键入,因此未采用。产品修复直接移除样式失效的来源。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**用 Node 折叠作为绘制证据。** 拒绝,因为它不执行 DOM 修改、布局或浏览器调度。聚焦重连用例同样不声称 GUI 提速。
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
en: b0d806604bc17006
|
||||
zh: 18dabe5dc2d0d419
|
||||
/deepseek-ai-dsh-client-ui-chat/grouped-rendering:
|
||||
en: 408664597c1480ed
|
||||
zh: 287a9b981337f288
|
||||
en: f78cbc73be5dc3ac
|
||||
zh: 893ba03d66b8ab65
|
||||
/deepseek-ai-dsh-client-ui-chat/scroll-ownership:
|
||||
en: 284737900cb75668
|
||||
zh: 12e0844082ff79ce
|
||||
|
||||
@@ -107,7 +107,7 @@ The Chat Builder publishes `bottomSource` per Node: true for visible Nodes belon
|
||||
|
||||
Live tool deltas share reasoning's frame-batched publication; durable calls and results publish immediately. Native and nested results retain event presentation metadata, so historical file and command labels use the operation’s location. Repeated deltas retain the Tool node when observed argument answers, the anchor, location, and visibility are unchanged. The Definition supplies the same lazy argument reader to tool rows and group detail, without tool-specific registration.
|
||||
|
||||
The process group uses a stable `div` layout box, a scroll body, and an uncapped content box that reports growth inside the body. Business styles must adapt spacing within and across groups, including hidden or empty members and the answer-spacing exception. The `conversation.chat.flow` slot adds a `display: contents` DOM anchor; row selectors match that anchor directly, without repeating its column ancestry. The running status shares the rows' parent, so its divider uses the same visible-sibling rules. The anchor contributes no spacing of its own. CSS variables do not belong in the Group Definition.
|
||||
The process group uses a stable `div` layout box, a scroll body, and an uncapped content box that reports growth inside the body. Business styles must adapt spacing within and across groups, including hidden or empty members and the answer-spacing exception. The `conversation.chat.flow` slot adds a `display: contents` DOM anchor; row selectors match that anchor directly, without repeating its column ancestry. The running status shares the rows' parent, so its divider uses the same visible-sibling rules. The anchor contributes no spacing of its own. The earlier-history control directly precedes the flow anchor; their sibling relationship supplies the leading gap. CSS variables do not belong in the Group Definition.
|
||||
|
||||
Running group titles, reasoning previews, tool and command rows, the active retry line, and the bottom running status use the shared [TextShimmer](../ui-primitives/README.md#component-catalog). Each row’s text shares one left-to-right highlight, including collapsed group titles; icons and hover chevrons retain their base appearance. Tool and process rows sit 6px apart. An expanded group title has 8px before its content, Assistant responses have 12px from adjacent process rows, and completed-Turn duration/status controls have 16px from their input and from a directly following visible row. A reply that follows the control after folded rows keeps the 12px reply gap, so folding never changes its spacing. Hidden and empty rows, including retained empty slot anchors, add no gap. The running “Deep diving” text retains its blue base while its theme-specific highlight sweeps across it.
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ Chat Builder 按节点发布 `bottomSource`:节点可见且属于已加载的
|
||||
|
||||
实时工具 delta 与推理共用按帧合并的发布节奏;持久调用和结果立即发布。原生与嵌套结果保留事件中的展示元数据,使历史文件与命令标签使用执行时的位置。当已读取参数的答案、锚点、位置及可见性均未变化时,重复 delta 保持 Tool 节点不变。Definition 向工具行和分组详情提供同一个懒计算参数读器,无需按工具注册。
|
||||
|
||||
过程组使用稳定的 `div` 布局盒子、滚动正文及不限高的内容盒子,后者报告正文内部的内容增长。业务样式必须适配组内及组边界间距,处理隐藏或空成员以及回答前的间距特例。`conversation.chat.flow` 插槽添加一个 `display: contents` DOM 锚点;行选择器直接匹配该锚点,不重复匹配其正文容器祖先。运行状态与各行共享父级,使分隔线使用同一套可见兄弟节点规则。锚点自身不产生间距。CSS 变量不属于 Group Definition。
|
||||
过程组使用稳定的 `div` 布局盒子、滚动正文及不限高的内容盒子,后者报告正文内部的内容增长。业务样式必须适配组内及组边界间距,处理隐藏或空成员以及回答前的间距特例。`conversation.chat.flow` 插槽添加一个 `display: contents` DOM 锚点;行选择器直接匹配该锚点,不重复匹配其正文容器祖先。运行状态与各行共享父级,使分隔线使用同一套可见兄弟节点规则。锚点自身不产生间距。更早历史控件紧邻流锚点之前,两者的兄弟关系决定首行间距。CSS 变量不属于 Group Definition。
|
||||
|
||||
运行中的分组标题、思考预览、工具与命令行、当前重试行以及底部运行状态使用共享的 [TextShimmer](../ui-primitives/README.zh.md#component-catalog)。每行的文字共用一道从左到右的高光,包括折叠的分组标题;图标和悬停箭头保留底色。工具和过程行之间相隔 6px;展开的分组标题与内容之间相隔 8px;Assistant 回复与相邻过程行之间相隔 12px;每轮完成/耗时控件与其输入、以及紧随其后的可见行之间保留 16px。控件与回复之间隔着已收起的行时,回复保持 12px 回复间距,因此折叠不会改变它的间距。隐藏行与空行(包括保留的空 slot 锚点)不产生间距。“深度求索”运行文字保留蓝色底色,并使用主题专用高光扫过文字。
|
||||
|
||||
|
||||
@@ -64,10 +64,11 @@
|
||||
|
||||
/* The flow slot's display:contents anchor preserves layout but not selector ancestry.
|
||||
`hidden="until-found"` retains a searchable zero-height box; hidden and empty
|
||||
Seats do not contribute spacing. */
|
||||
Seats do not contribute spacing. The older-history control precedes the anchor;
|
||||
an ancestor :has() here also invalidates the history on unrelated descendant edits. */
|
||||
:is(.column, :global([data-slot="conversation.chat.flow"])) > :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty)))
|
||||
~ :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))),
|
||||
.column:has(> .older) > :global([data-slot="conversation.chat.flow"])
|
||||
.column > .older + :global([data-slot="conversation.chat.flow"])
|
||||
> :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))) {
|
||||
margin-top: var(--dsh-chat-flow-gap, 6px);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user