Compare commits

..

1 Commits

Author SHA1 Message Date
Aiden da6035431d fix(tui): clarify unavailable reasoning summaries 2026-08-20 05:39:11 +00:00
5 changed files with 18 additions and 53 deletions
@@ -1,7 +1,6 @@
import { expect, test } from "@playwright/test"
import {
assistantMessage,
partUpdated,
reasoningPart,
setupTimeline,
status,
@@ -92,26 +91,3 @@ test("does not infer reasoning visibility from provider identity", async ({ page
await expect(page.locator('[data-timeline-part-id*="reasoning"]')).toHaveCount(0)
await expect(page.locator('[data-timeline-part-id="prt_provider_text"]')).toBeVisible()
})
test("replaces Thinking with a plain Thought label when opaque reasoning completes", async ({ page }) => {
const reasoningID = "prt_reasoning_opaque"
const reasoning = {
...reasoningPart(reasoningID, ""),
metadata: { anthropic: { redactedData: "opaque" } },
}
const timeline = await setupTimeline(page, {
messages: [userMessage(), assistantMessage([reasoning], { completed: false })],
settings: { showReasoningSummaries: true },
})
await timeline.send(status("busy"), 150)
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(0)
await timeline.send(partUpdated({ ...reasoning, time: { start: 1700000001000, end: 1700000002000 } }), 150)
const completed = page.locator(`[data-timeline-part-id="${reasoningID}"]`)
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
await expect(completed).toHaveText("Thought")
await expect(completed.locator("button")).toHaveCount(0)
})
@@ -295,12 +295,6 @@
color: var(--v2-text-text-muted);
line-height: var(--line-height-normal);
[data-slot="reasoning-part-label"] {
display: block;
margin-top: 16px;
font-size: 13px;
}
[data-component="markdown"] {
margin-top: 16px;
font-style: normal;
@@ -715,14 +715,10 @@ export function renderable(part: PartType, showReasoningSummaries = true) {
return true
}
if (part.type === "text") return !!part.text?.trim()
if (part.type === "reasoning") return showReasoningSummaries && (!!part.text?.trim() || opaqueReasoning(part))
if (part.type === "reasoning") return showReasoningSummaries && !!part.text?.trim()
return !!PART_MAPPING[part.type]
}
function opaqueReasoning(part: ReasoningPart) {
return !part.text.trim() && Boolean(part.metadata) && part.time.end !== undefined
}
export { partDefaultOpen } from "./part-default-open"
export function AssistantParts(props: {
@@ -1762,7 +1758,6 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) {
const data = useData()
const i18n = useI18n()
const part = () => props.part as ReasoningPart
const streaming = createMemo(
() => props.message.role === "assistant" && typeof (props.message as AssistantMessage).time.completed !== "number",
@@ -1770,18 +1765,11 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) {
const text = () => readPartText(data.store.part_text_accum_delta, part())
return (
<div data-component="reasoning-part" data-timeline-part-id={part().id}>
<Show
when={text()}
fallback={
<Show when={opaqueReasoning(part())}>
<span data-slot="reasoning-part-label">{i18n.t("ui.messagePart.reasoning.opaque")}</span>
</Show>
}
>
<Show when={text()}>
<div data-component="reasoning-part" data-timeline-part-id={part().id}>
<PacedMarkdown text={text()} cacheKey={part().id} streaming={streaming()} />
</Show>
</div>
</div>
</Show>
)
}
+13 -5
View File
@@ -1608,7 +1608,7 @@ function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: Ass
const syntax = createSyntaxStyleMemo(() => generateSubtleSyntax(theme))
const toggle = () => {
if (!inMinimal() || opaque()) return
if (!inMinimal()) return
setExpanded((prev) => !prev)
}
@@ -1623,14 +1623,19 @@ function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: Ass
>
<box onMouseUp={toggle}>
<ReasoningHeader
toggleable={inMinimal() && !opaque()}
toggleable={inMinimal()}
open={!inMinimal() || expanded()}
done={isDone()}
title={summary().title}
duration={isDone() ? Locale.duration(duration()) : undefined}
encrypted={opaque()}
opaque={opaque()}
/>
</box>
<Show when={opaque() && (!inMinimal() || expanded())}>
<box paddingLeft={inMinimal() ? 2 : 0} marginTop={1}>
<text fg={theme.info}>OpenCode: No reasoning summary available</text>
</box>
</Show>
<Show when={!opaque() && (!inMinimal() || expanded()) && summary().body}>
<box paddingLeft={inMinimal() ? 2 : 0} marginTop={1}>
<code
@@ -1655,7 +1660,7 @@ function ReasoningHeader(props: {
done: boolean
title: string | null
duration?: string
encrypted?: boolean
opaque?: boolean
}) {
const { theme } = useTheme()
const fg = () =>
@@ -1663,7 +1668,10 @@ function ReasoningHeader(props: {
? RGBA.fromValues(theme.warning.r, theme.warning.g, theme.warning.b, theme.thinkingOpacity)
: theme.warning
const completed = () => {
if (props.encrypted) return `Thought (encrypted)${props.duration ? ` · ${props.duration}` : ""}`
if (props.opaque) {
const detail = props.duration ? ` · ${props.duration}` : ""
return `${props.toggleable ? (props.open ? "- " : "+ ") : ""}Thought${detail}`
}
const detail = [props.title, props.duration].filter(Boolean).join(" · ")
return `${props.toggleable ? (props.open ? "- " : "+ ") : ""}Thought${detail ? `: ${detail}` : ""}`
}
-1
View File
@@ -102,7 +102,6 @@ export const dict: Record<string, string> = {
"ui.messagePart.review.title": "Review your answers",
"ui.messagePart.questions.dismissed": "Questions dismissed",
"ui.messagePart.compaction": "Session compacted",
"ui.messagePart.reasoning.opaque": "Thought",
"ui.messagePart.context.read.one": "{{count}} read",
"ui.messagePart.context.read.other": "{{count}} reads",
"ui.messagePart.context.search.one": "{{count}} search",