fix trace scroll preservation

This commit is contained in:
Codex
2026-05-25 01:26:13 +00:00
parent b3d298c3e5
commit 3d097af835
3 changed files with 46 additions and 0 deletions
+6
View File
@@ -108,6 +108,12 @@ session identifiers, the visible detail must include a short redacted
[Code Agent readiness](code-agent-chat-readiness.md) 合同返回 completed
且非空的 assistant response 前,Workbench 不得暗示已有真实 assistant reply。
Code Agent trace 在消息卡片内使用独立滚动容器展示。SSE、轮询或“回放 trace”
更新只能刷新内容,不得重置用户已展开/折叠状态,也不得把
`.message-trace-events` 的内部滚动位置跳回顶部;渲染前后必须按
`messageId/traceId` 保存并恢复 trace list 的 `scrollTop/scrollLeft`。完整 trace
可通过复制/下载获取,页面内默认保持“显示全部 + 内部滚动”的单一模式。
## Lightweight Checks
Use the existing lightweight checks unless a task explicitly authorizes a
+31
View File
@@ -180,6 +180,7 @@ const state = {
chatMessages: [],
traceStreams: new Map(),
traceDetailsOpen: new Map(),
traceScrollPositions: new Map(),
canceledTraces: new Set(),
currentRequest: null,
sessionStatus: null,
@@ -791,6 +792,7 @@ function initCommandBar() {
for (const close of state.traceStreams.values()) close();
state.traceStreams.clear();
state.traceDetailsOpen.clear();
state.traceScrollPositions.clear();
state.chatMessages = [];
state.conversationId = null;
state.sessionId = null;
@@ -2004,6 +2006,7 @@ function workbenchApiSurfaceStatus(live, coreProbes = [live.healthLive, live.res
}
function renderConversation() {
captureTraceScrollPositions();
const introMessages = [
{
role: "system",
@@ -2014,6 +2017,30 @@ function renderConversation() {
codeAgentStatusMessage(state.codeAgentAvailability)
];
replaceChildren(el.conversationList, ...[...introMessages, ...state.chatMessages].map(messageCard));
restoreTraceScrollPositions();
}
function captureTraceScrollPositions(root = el.conversationList) {
for (const list of root.querySelectorAll(".message-trace-events[data-trace-ui-key]")) {
rememberTraceScrollPosition(list.dataset.traceUiKey, list);
}
}
function rememberTraceScrollPosition(traceUiKey, list) {
if (!traceUiKey || !list) return;
state.traceScrollPositions.set(traceUiKey, {
top: list.scrollTop,
left: list.scrollLeft
});
}
function restoreTraceScrollPositions(root = el.conversationList) {
for (const list of root.querySelectorAll(".message-trace-events[data-trace-ui-key]")) {
const position = state.traceScrollPositions.get(list.dataset.traceUiKey);
if (!position) continue;
list.scrollTop = Math.min(position.top, Math.max(0, list.scrollHeight - list.clientHeight));
list.scrollLeft = Math.min(position.left, Math.max(0, list.scrollWidth - list.clientWidth));
}
}
function renderHardwareStatus(m3Status) {
@@ -4066,6 +4093,10 @@ function messageTracePanel(message) {
summary.textContent = runnerTraceHeadline(message, trace);
const list = document.createElement("ol");
list.className = "message-trace-events";
if (traceUiKey) {
list.dataset.traceUiKey = traceUiKey;
list.addEventListener("scroll", () => rememberTraceScrollPosition(traceUiKey, list), { passive: true });
}
const events = Array.isArray(trace?.events) ? trace.events : [];
const rows = traceDisplayRows(trace, events);
if (events.length === 0) {
+9
View File
@@ -809,12 +809,21 @@ assert.match(app, /function messageTraceToolbar/);
assert.match(app, /function messageTraceCountText/);
assert.match(app, /显示全部\s+\$\{displayTotal\}\s+\/\s+原始\s+\$\{rawTotal\}/);
assert.match(app, /traceDetailsOpen:\s*new Map\(\)/);
assert.match(app, /traceScrollPositions:\s*new Map\(\)/);
assert.match(app, /state\.traceDetailsOpen\.clear\(\)/);
assert.match(app, /state\.traceScrollPositions\.clear\(\)/);
assert.match(app, /function messageTraceUiKey/);
assert.match(app, /function defaultTraceDetailsOpen/);
assert.match(app, /function captureTraceScrollPositions/);
assert.match(app, /function restoreTraceScrollPositions/);
assert.match(app, /function rememberTraceScrollPosition/);
assert.match(functionBody(app, "renderConversation"), /captureTraceScrollPositions\(\)/);
assert.match(functionBody(app, "renderConversation"), /restoreTraceScrollPositions\(\)/);
assert.match(functionBody(app, "messageTracePanel"), /state\.traceDetailsOpen\.get\(traceUiKey\)/);
assert.match(functionBody(app, "messageTracePanel"), /defaultTraceDetailsOpen\(message\)/);
assert.match(functionBody(app, "messageTracePanel"), /state\.traceDetailsOpen\.set\(traceUiKey,\s*details\.open\)/);
assert.match(functionBody(app, "messageTracePanel"), /list\.dataset\.traceUiKey\s*=\s*traceUiKey/);
assert.match(functionBody(app, "messageTracePanel"), /rememberTraceScrollPosition\(traceUiKey,\s*list\)/);
assert.match(functionBody(app, "messageTracePanel"), /list\.dataset\.traceMode\s*=\s*"all"/);
assert.match(functionBody(app, "messageTracePanel"), /renderTraceEventList\(list,\s*rows\)/);
assert.doesNotMatch(app, /CODE_AGENT_TRACE_PREVIEW_LIMIT|tracePreviewRows|收起为最近|展开全部|data-trace-mode="tail"/);