fix(chat): keep the reply gap on a folded final answer

A folded Turn gave its final answer the control's 16px clearance through
compactAnswer and data-turn-process-answer, because hidden process rows
keep the answer from matching the control's adjacent-row rule. With every
reply kept, the final answer no longer follows the control, so that 16px
only widened the gap above it. In both choices the margin also switched
from 12px to 16px when the closing rows became hidden at the end of a
next-message fold, moving the answer down about 4px.

A reply separated from the control by folded rows now keeps the 12px reply
gap in both choices; only a row directly following the control receives
16px, and no reply changes its spacing when a fold finishes.
This commit is contained in:
imccyu committed 2026-10-18 23:01:23 +08:00
1 parent 8598307c1e
commit c458eab0a0
16 files changed
+32 -80

No files matched your search

@@ -18,14 +18,14 @@
en: 352c5bdf25c09c62
zh: 24878584535b4d57
/agent-note-keep-every-assistant-reply-when-a-turn-folds/decision/implementation:
en: 96ec890325a9d88f
zh: 4cc13ec9117f9a67
en: e40156780daec8fe
zh: 4447a61c2b1574c8
/agent-note-keep-every-assistant-reply-when-a-turn-folds/alternatives-considered:
en: 28694c0a82df72ac
zh: 8f6f81c833cf8f2c
/agent-note-keep-every-assistant-reply-when-a-turn-folds/consequences:
en: 07a2a18edd197c62
zh: 4f93fdbf0bce61b5
en: d648f688c7e6c87d
zh: d84f1a43de8992b2
/agent-note-keep-every-assistant-reply-when-a-turn-folds/testing:
en: 73760d0f673fad28
zh: 98392c167db5bd9a
en: 70133688614f2a5c
zh: 931a2710211ced3f
@@ -60,7 +60,7 @@ A Turn with replies but no process content keeps the control's title without a c
### Implementation
Only the rule that decides process membership changes, and it follows the setting. `TurnProcessSpec` with its answer fields (`answerAnchorSeq`, `answerStep`, `inlineReasoning`, `messageCount`), the `latestAnswer` selection, manual disclosure saved per answer Step, and the final answer's 16px spacing (`compactAnswer`) stay as they were; with Final answer only they still apply and the behavior matches the previous rule.
Only the rule that decides process membership changes, and it follows the setting. `TurnProcessSpec` with its answer fields (`answerAnchorSeq`, `answerStep`, `inlineReasoning`, `messageCount`), the `latestAnswer` selection, and manual disclosure saved per answer Step stay as they were; with Final answer only they still apply and the behavior matches the previous rule.
| Location | Content |
|---|---|
@@ -99,7 +99,8 @@ The [Chat Node business rules](../../../../packages/client/ui-chat/src/client/co
- Gains: Turns that receive a notice, record keeping, or a tool call after answering no longer lose the answer; subagent tasks that end with a structured result are no longer blank; readers who want the previous presentation can switch back with one choice.
- Collapsed text grows by an estimated 10% in main Sessions and 20% in subagent views. The added text includes actual conclusions that previously folded away as well as redundancy from one- or two-line action previews; subagent views are rarely read, so the absolute cost is small.
- After collapsing, consecutive replies no longer show the process between them and may read as one passage; each reply remains its own row with the original spacing.
- The retained answer structure leaves two small asymmetries under `all`: the 16px collapsed spacing still goes to the final answer while the first reply after the control uses the ordinary 12px reply gap, and `data-turn-process-messages` still counts the replies before the final answer although they are visible. Moving the 16px to the first reply would change that row's spacing when collapsing starts and produce a 4px reversal the fold animation cannot absorb (`chat-scroll-contract.e2e.ts`).
- A collapsed Turn no longer gives its final answer the control's 16px clearance: in both choices, a reply separated from the control by folded rows keeps the 12px reply gap, and only a row directly following the control receives 16px. Because no reply changes its spacing when the fold finishes, the final answer no longer moves down about 4px at the end of a fold (#6323).
- The retained answer structure leaves one small asymmetry under `all`: `data-turn-process-messages` still counts the replies before the final answer although they are visible.
- These proportions are rough estimates and vary with ways of working and models.
## Testing
@@ -107,4 +108,4 @@ The [Chat Node business rules](../../../../packages/client/ui-chat/src/client/co
- `packages/client/ui-chat/tests/chat-view.client.spec.tsx`: replies from every Step stay visible while reasoning and tools fold, a Turn without a text-only ending still shows its replies, and a reply-only Turn offers no collapse; switching between `all` and `final` does not rebuild rows; cases that used an intermediate reply as a process stand-in now use reasoning-only rows.
- `packages/client/ui-chat/tests/presentation-policy.client.spec.tsx`: fixed policy objects for the 16 combinations and subscriptions to all three preferences.
- `packages/client/ui-chat/tests/transcript-view-policy.client.spec.ts`, `transcript-view-row.client.spec.tsx`, and `config.host.spec.ts`: default, persistence, and adoption; the settings row's position, Chinese and English copy, and hiding in Verbose; Host configuration validation and restoration.
- Web e2e: `turn-tail-actions.e2e.ts` and `chat-flow-layout.e2e.ts` assert that an intermediate reply follows a folded control at the 12px reply gap; the settings and folded-Turn aria goldens include the new settings row and intermediate replies.
- Web e2e: `turn-tail-actions.e2e.ts` and `chat-flow-layout.e2e.ts` assert that the first visible reply after a folded control keeps the 12px reply gap; the settings and folded-Turn aria goldens include the new settings row and intermediate replies.
@@ -60,7 +60,7 @@ Status: implemented
### 实现
只改"谁是过程成员"这一条规则,并让它跟随设置。`TurnProcessSpec` 及其答案字段(`answerAnchorSeq`、`answerStep`、`inlineReasoning`、`messageCount`)、`latestAnswer` 选取、按答案 Step 保存的手动开合、最终答案的 16px 间距(`compactAnswer`)保持原样;选「仅最终回答」时这些结构照旧生效,行为与原规则一致。
只改"谁是过程成员"这一条规则,并让它跟随设置。`TurnProcessSpec` 及其答案字段(`answerAnchorSeq`、`answerStep`、`inlineReasoning`、`messageCount`)、`latestAnswer` 选取、按答案 Step 保存的手动开合保持原样;选「仅最终回答」时这些结构照旧生效,行为与原规则一致。
| 位置 | 内容 |
|---|---|
@@ -99,7 +99,8 @@ Status: implemented
- 收益:回答之后又有通知、记账或工具调用的 Turn 不再丢失回答;以结构化结果收尾的子任务不再整轮空白;想要原呈现的读者可以一键切回。
- 收起后的正文按估计在主会话多出约 10%,在子 agent 视图多出约 20%。多出的内容里既有原本被折掉的实际结论,也有一两行动作预告带来的冗余;子 agent 视图阅读频率低,代价按绝对量计较小。
- 收起后连续的回复之间不再显示过程,几条回复可能被连读成一段;每条回复仍是独立行,保持原有间距。
- 保留的答案结构在 `all` 下留下两处小的不对称:收起时 16px 间距仍给最终答案,紧跟控件的第一条回复使用普通的 12px 回复间距;`data-turn-process-messages` 仍统计最终答案之前的回复数,尽管它们已可见。把 16px 移给第一条回复会让该行在收起开始时改变间距,产生折叠动画无法抵消的 4px 回跳(`chat-scroll-contract.e2e.ts`)。
- 收起的 Turn 不再给最终答案额外的控件 16px 间距:两种选项下,与控件之间隔着已收起行的回复都保持 12px 回复间距,只有紧跟控件的那一行得到 16px。折叠结束时没有回复改变间距,最终答案不再在折叠末尾下移约 4px(#6323)。
- 保留的答案结构在 `all` 下留下一处小的不对称:`data-turn-process-messages` 仍统计最终答案之前的回复数,尽管它们已可见。
- 上述比例是粗略估计,不同的工作方式和模型下会有差异。
## Testing
@@ -107,4 +108,4 @@ Status: implemented
- `packages/client/ui-chat/tests/chat-view.client.spec.tsx`:收起后各 Step 的回复可见、推理与工具收起、无纯文字收尾的 Turn 仍显示回复、只有回复的 Turn 不提供折叠;`all` 与 `final` 实时切换不重建行;把阶段回复当作过程替身的用例改为纯推理行。
- `packages/client/ui-chat/tests/presentation-policy.client.spec.tsx`:16 种组合的固定策略对象与三项偏好订阅。
- `packages/client/ui-chat/tests/transcript-view-policy.client.spec.ts`、`transcript-view-row.client.spec.tsx`、`config.host.spec.ts`:默认值、持久化与采纳,设置行位置、中英文案与完全展开时隐藏,Host 配置校验与恢复。
- Web e2e:`turn-tail-actions.e2e.ts` 与 `chat-flow-layout.e2e.ts` 断言收起后阶段回复以 12px 回复间距紧跟控件;设置页与收起后 aria 黄金快照包含新设置行与阶段回复。
- Web e2e:`turn-tail-actions.e2e.ts` 与 `chat-flow-layout.e2e.ts` 断言收起后控件之后第一条可见回复保持 12px 回复间距;设置页与收起后 aria 黄金快照包含新设置行与阶段回复。
+8 -11
View File
@@ -55,17 +55,14 @@ it.each(['turn-tail-actions', 'goal-multi-turn-actions', 'present'] as const)(
return { top, bottom }
})
const processBottom = await process.evaluate(element => element.getBoundingClientRect().bottom)
if (scenario === 'present') {
expect(answerBounds.top).toBe(processBottom + 16)
} else {
// Every reply stays visible: the intermediate reply follows the folded control at the 12px reply gap.
const leadTop = await page.getByText('Reading the workspace now.', { exact: true }).evaluate((element) => {
const seat = element.closest('[data-chat-flow-kind="assistant-step"]')
if (seat === null) throw new Error('intermediate reply has no Chat seat')
return seat.getBoundingClientRect().top
})
expect(leadTop).toBe(processBottom + 12)
}
// Hidden process rows separate the folded control from the first visible reply, so it keeps the 12px reply gap.
const lead = scenario === 'present' ? answer : page.getByText('Reading the workspace now.', { exact: true })
const leadTop = await lead.evaluate((element) => {
const seat = element.closest('[data-chat-flow-kind="assistant-step"]')
if (seat === null) throw new Error('first visible reply has no Chat seat')
return seat.getBoundingClientRect().top
})
expect(leadTop).toBe(processBottom + 12)
if (scenario === 'present') {
const geometry = await page.locator('[data-presented-files-row]').evaluate((grid) => {
const root = grid.parentElement
+1 -1
View File
@@ -249,7 +249,7 @@ describe('web e2e: assistant IconActions wait for the turn to end', () => {
expect(await process.evaluate(element => getComputedStyle(element).borderBottomWidth)).toBe('1px')
const processBottom = await process.evaluate(element =>
element.closest<HTMLElement>('[data-chat-flow-kind="turn-process"]')?.getBoundingClientRect().bottom)
// Every reply stays visible: the intermediate reply follows the folded control at the 12px reply gap.
// The hidden reasoning row separates the folded control from the intermediate reply, so it keeps the 12px reply gap.
const leadTop = await page.getByText('Reading the workspace now.', { exact: true }).evaluate(element =>
element.closest<HTMLElement>('[data-chat-flow-kind="assistant-step"]')?.getBoundingClientRect().top)
expect(leadTop).toBe((processBottom ?? 0) + 12)
+2 -2
View File
@@ -33,8 +33,8 @@
en: 4cd7c81d70d2f365
zh: 18eca6d16e44b6c2
/deepseek-ai-dsh-client-ui-chat/grouped-rendering:
en: 81d7ca7595c34d39
zh: 29eb8248c75e9ea6
en: 408664597c1480ed
zh: 287a9b981337f288
/deepseek-ai-dsh-client-ui-chat/scroll-ownership:
en: 284737900cb75668
zh: 12e0844082ff79ce
+1 -1
View File
@@ -109,7 +109,7 @@ Live tool deltas share reasoning's frame-batched publication; durable calls and
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.
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 around them. 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.
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.
The running status shows a whale whose APNG mask inherits the text color. CSS selects the static SVG when reduced motion or forced colors are active, or alpha masks are unsupported. The build embeds the image in the plugin stylesheet; React does not pass image data through DOM styles.
+1 -1
View File
@@ -109,7 +109,7 @@ Chat Builder 按节点发布 `bottomSource`:节点可见且属于已加载的
过程组使用稳定的 `div` 布局盒子、滚动正文及不限高的内容盒子,后者报告正文内部的内容增长。业务样式必须适配组内及组边界间距,处理隐藏或空成员以及回答前的间距特例。`conversation.chat.flow` 插槽添加一个 `display: contents` DOM 锚点;行选择器直接匹配该锚点,不重复匹配其正文容器祖先。运行状态与各行共享父级,使分隔线使用同一套可见兄弟节点规则。锚点自身不产生间距。CSS 变量不属于 Group Definition。
运行中的分组标题、思考预览、工具与命令行、当前重试行以及底部运行状态使用共享的 [TextShimmer](../ui-primitives/README.zh.md#component-catalog)。每行的文字共用一道从左到右的高光,包括折叠的分组标题;图标和悬停箭头保留底色。工具和过程行之间相隔 6px;展开的分组标题与内容之间相隔 8px;Assistant 回复与相邻过程行之间相隔 12px;每轮完成/耗时控件周围保留 16px。隐藏行与空行(包括保留的空 slot 锚点)不产生间距。“深度求索”运行文字保留蓝色底色,并使用主题专用高光扫过文字。
运行中的分组标题、思考预览、工具与命令行、当前重试行以及底部运行状态使用共享的 [TextShimmer](../ui-primitives/README.zh.md#component-catalog)。每行的文字共用一道从左到右的高光,包括折叠的分组标题;图标和悬停箭头保留底色。工具和过程行之间相隔 6px;展开的分组标题与内容之间相隔 8px;Assistant 回复与相邻过程行之间相隔 12px;每轮完成/耗时控件与其输入、以及紧随其后的可见行之间保留 16px。控件与回复之间隔着已收起的行时,回复保持 12px 回复间距,因此折叠不会改变它的间距。隐藏行与空行(包括保留的空 slot 锚点)不产生间距。“深度求索”运行文字保留蓝色底色,并使用主题专用高光扫过文字。
运行状态显示一只鲸鱼,其 APNG 遮罩继承文字颜色。启用减少动态效果或强制颜色模式、或浏览器不支持 alpha 遮罩时,CSS 选择静态 SVG。构建将图片嵌入插件样式表;React 不通过 DOM 样式传递图片数据。
@@ -114,10 +114,6 @@ export const ChatNodeSeat = memo(function ChatNodeSeat({
])
const controllerInactive = routedNode?.kind === 'turn-process'
&& foldCompleted && !foldable
const compactAnswer = processAnswer
&& foldable
&& processPresentation.compactAnswer
&& !processOpen
const processHidden = controllerInactive || (foldable && processMember && !processOpen)
const revealProcess = useCallback(() => {
if (processMember) setOpen(true)
@@ -167,7 +163,6 @@ export const ChatNodeSeat = memo(function ChatNodeSeat({
data-chat-turn={turn}
data-turn-process-member={processMember || undefined}
data-turn-process-hidden={processHidden || undefined}
data-turn-process-answer={compactAnswer || undefined}
>
{renderSlot('conversation.chat.node', routedOwner, {
entryKey: routedNode.kind,
@@ -117,7 +117,6 @@
running Turn's control renders empty until the Turn closes; an echo after it
is still an opening input of that Turn, so it keeps the input gap. */
.flowItem[data-chat-flow-kind="turn-process"],
.flowItem[data-turn-process-answer],
:is(.column, :global([data-slot="conversation.chat.flow"])) > [data-chat-flow-kind="turn-process"]:not(:empty, :has(> [data-slot="conversation.chat.node"]:empty)) + :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))):not(.running):not(:has(> [data-slot="conversation.chat.node"] > [data-chat-steering-batch])),
:is(.column, :global([data-slot="conversation.chat.flow"])) > [data-chat-flow-kind="turn-process"] + :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))):not(.running):not([data-submission-echo], [data-pending-steering]):not(:has(> [data-slot="conversation.chat.node"] > [data-chat-steering-batch])) {
--dsh-chat-flow-gap: 16px;
@@ -96,7 +96,6 @@ export interface ChatTurnProcessPresentation {
readonly hasNonReplyProcess: boolean
/** A visible input after process output prevents one disclosure from hiding its surrounding groups. */
readonly hasInterleavedInput: boolean
readonly compactAnswer: boolean
}
/** Compatibility projection backing the composer stats pills (StatsPills.tsx) and the legacy top-level snapshot fields. */
@@ -39,8 +39,8 @@
en: 8e775c43878ae31a
zh: 4c1063c0071ebdbd
/chat-conversation-node-rules/display-modes/group-title-rules:
en: 2b23813edc87faed
zh: bc5ddb49c9d128e7
en: 8c5b57a66fe0247c
zh: 5a9e37f255761ac6
/chat-conversation-node-rules/process-grouping:
en: 3d28b3bb8757bf55
zh: 3b0f451b48db6048
@@ -187,7 +187,7 @@ The labels below describe recorded activity, not successful outcomes. For exampl
English lowercases the initial letter of joined labels after the first. Closing a group immediately selects the completed summary; the 150ms minimum applies to running-title changes, not to delaying completion. Detailed hides group headers in running Turns, including groups ended by a reply or steering before their Turn ends. Verbose also hides historical group headers.
Group headers show a category icon, replace it with a down arrow on hover or keyboard focus, and show an up arrow while open. Manually expanded group bodies use 6px row spacing, a `min(400px, 50vh)` height cap, and 24px directional fades. Wheel scrolling can continue into the outer transcript at an edge. Detailed removes the group-level cap in running Turns; Verbose applies this layout to historical Turns as well. Process rows retain 6px spacing in both modes; Assistant responses have 12px clearance. Group headers have the same row height as process rows. Expanded group titles sit 8px above their content; Turn process controls retain 16px clearance.
Group headers show a category icon, replace it with a down arrow on hover or keyboard focus, and show an up arrow while open. Manually expanded group bodies use 6px row spacing, a `min(400px, 50vh)` height cap, and 24px directional fades. Wheel scrolling can continue into the outer transcript at an edge. Detailed removes the group-level cap in running Turns; Verbose applies this layout to historical Turns as well. Process rows retain 6px spacing in both modes; Assistant responses have 12px clearance. Group headers have the same row height as process rows. Expanded group titles sit 8px above their content; Turn process controls retain 16px clearance from their input and from a directly following visible row; a reply separated from the control by folded rows keeps the 12px reply gap, so folding never changes its spacing.
An open capped group follows content growth only while its own scroll position is at the bottom. Scrolling away pauses that group's following; returning to the bottom resumes it, independently of outer transcript following. Manually opening an unclosed group starts at the bottom and follows growth; manually opening a closed group starts at the top with following disabled, even when its initial content fits without scrolling. Closing the group in the data or restoring its height cap through a mode change does not reset an already-open reader's position. Browser find retains its own reveal position.
@@ -187,7 +187,7 @@ description: "Chat 过程分组与活动摘要的明细业务规则。"
英文组合标题将第一项之后各项的首字母转为小写。组结束时立即切换完成摘要;150ms 最短保留时间针对运行中标题变化,不延迟完成状态。详细模式隐藏运行中轮次的组头,包括被回复或 steering 截断、但所属轮次尚未结束的小组;完全展开模式也隐藏历史组头。
组头显示类别图标,悬停或键盘聚焦时替换为向下箭头,展开时显示向上箭头。手动展开的组正文使用 6px 行间距、`min(400px, 50vh)` 限高及 24px 方向渐隐;到达边缘时滚轮可以继续滚动外层文本记录。详细模式对运行中轮次取消组级限高;完全展开模式对历史轮次也采用该布局。两种模式下过程行均保留 6px 间距,Assistant 回复周围保留 12px 间距。组头与过程行同高。展开组头与内容相隔 8px,整轮过程控件周围保留 16px 间距。
组头显示类别图标,悬停或键盘聚焦时替换为向下箭头,展开时显示向上箭头。手动展开的组正文使用 6px 行间距、`min(400px, 50vh)` 限高及 24px 方向渐隐;到达边缘时滚轮可以继续滚动外层文本记录。详细模式对运行中轮次取消组级限高;完全展开模式对历史轮次也采用该布局。两种模式下过程行均保留 6px 间距,Assistant 回复周围保留 12px 间距。组头与过程行同高。展开组头与内容相隔 8px,整轮过程控件与其输入、以及紧随其后的可见行之间保留 16px 间距;与控件之间隔着已收起行的回复保持 12px 回复间距,因此折叠不会改变它的间距。
已展开的限高组仅在自己的滚动位置处于底部时跟随内容增长。滚离底部暂停该组跟随,滚回底部后恢复,与外层文本记录的跟随状态独立。手动展开未关闭的组时定位到底部并跟随增长;手动展开已关闭的组时从顶部开始且不启用跟随,即使初始内容无需滚动即可容纳。数据中的组变为已关闭,或通过模式切换恢复限高时,不重置已展开组的阅读位置。浏览器查找沿用自身的展开定位。
@@ -21,8 +21,7 @@ function samePresentation(
&& left.turnClosed === right.turnClosed
&& left.hasExternalProcess === right.hasExternalProcess
&& left.hasNonReplyProcess === right.hasNonReplyProcess
&& left.hasInterleavedInput === right.hasInterleavedInput
&& left.compactAnswer === right.compactAnswer)
&& left.hasInterleavedInput === right.hasInterleavedInput)
}
function derivePresentation(
@@ -51,14 +50,12 @@ function derivePresentation(
let hasExternalProcess = false
let hasNonReplyProcess = false
let hasInterleavedInput = false
let compactAnswer = true
for (const key of keys) {
const node = nodes.get(key) as ChatNode | undefined
if (node === undefined || !isVisibleChatNode(node) || node.kind === 'turn-process') continue
if (isAtOrOutside(node, 'input')
&& (openingHumanAnchor === undefined || node.anchorSeq > openingHumanAnchor)) {
hasInterleavedInput = true
if (spec.answerAnchorSeq === null || node.anchorSeq < spec.answerAnchorSeq) compactAnswer = false
}
if (isAtOrOutside(node, 'turn')
|| node.anchorSeq < spec.processStartSeq
@@ -80,7 +77,6 @@ function derivePresentation(
hasExternalProcess,
hasNonReplyProcess,
hasInterleavedInput,
compactAnswer,
}
}
@@ -2740,23 +2740,6 @@ describe('ChatView', () => {
expect(view.getByText('final answer').closest('[hidden]')).toBeNull()
})
it('keeps ordinary spacing when steering separates the process control from its answer', () => {
const h = makeHarness({
nodes: [
user(1, 'question'),
reasoningAssistant(2, 'inspect', 1, 1),
steering(3, 'also mention safety', 1),
assistant(4, 'final answer', 1, 2),
],
turnEnds: new Map([[1, 5]]),
})
const view = render(<h.ChatView {...h.props} />)
const answer = view.container.querySelector<HTMLElement>('[data-chat-flow-kind="assistant-step"]:not([hidden])')
expect(view.getByText('also mention safety')).toBeTruthy()
expect(answer?.hasAttribute('data-turn-process-answer')).toBe(false)
})
it.each(['compact', 'standard', 'detailed'] as const)(
'preserves steering-separated process groups in %s mode after Turn completion', (mode) => {
const nodes = [
@@ -2818,25 +2801,6 @@ describe('ChatView', () => {
},
)
it('keeps compact answer spacing after multiple opening steering inputs', () => {
const h = makeHarness({
nodes: [
steering(1, 'question', 1),
steering(2, 'also mention safety', 1),
reasoningAssistant(3, 'inspect', 1, 1),
assistant(4, 'final answer', 1, 2),
],
turnEnds: new Map([[1, 5]]),
})
const view = render(<h.ChatView {...h.props} />)
const answer = view.container.querySelector<HTMLElement>('[data-chat-flow-kind="assistant-step"]:not([hidden])')
expect(view.getByText('also mention safety')).toBeTruthy()
expect(answer?.hasAttribute('data-turn-process-answer')).toBe(true)
fireEvent.click(turnProcessControl(view.container)!)
expect(answer?.hasAttribute('data-turn-process-answer')).toBe(false)
})
it('keeps a live Turn expanded and folds it once at turn/end', withClock(3_000, () => {
const process = reasoningAssistant(2, 'inspect', 1, 1)
const h = makeHarness({