Skip to content

Commit 60cd8ac

Browse files
fix(acp): stop italic thinking text from clipping
Layout the thought body inside the padded viewport so the last glyphs do not open a horizontal scrollbar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a142827 commit 60cd8ac

2 files changed

Lines changed: 43 additions & 2 deletions

File tree

‎src/widgets/AcpMessageWidget.cpp‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ constexpr const char *kFrameStyleAssistant =
8383
"AcpMessageWidget[role=\"assistant\"][goalSet=\"true\"] { background: rgba(180, 140, 50, 48); border: 1px solid rgba(180, 140, 50, 80); border-radius: 6px; }";
8484
constexpr const char *kFrameStyleThought =
8585
"AcpMessageWidget[role=\"thought\"] { background: palette(base); border-radius: 6px; }";
86+
// Extra left inset under "Thinking…". Not in the layout margins — those wrap
87+
// the header too. Italic glyphs also overhang their advance by a few px, so
88+
// wrap width must leave that slack inside the viewport or the last letters
89+
// clip and QTextBrowser opens a horizontal scroll range.
90+
constexpr int kThoughtBrowserPadLeft = 4;
91+
constexpr int kThoughtItalicRightSlack = 4;
8692
constexpr const char *kFrameStyleSystem =
8793
"AcpMessageWidget[role=\"system\"] { background: rgba(180, 140, 50, 32); border: 1px solid rgba(180, 140, 50, 60); border-radius: 6px; }";
8894

@@ -219,7 +225,10 @@ AcpMessageWidget::AcpMessageWidget(QString role, QWidget *parent)
219225
m_layout->addWidget(m_thoughtHeader);
220226

221227
m_browser = new QTextBrowser(this);
222-
m_browser->setStyleSheet(QStringLiteral("QTextBrowser { background: transparent; border: none; font-style: italic; padding-left: 4px; }"));
228+
m_browser->setStyleSheet(
229+
QStringLiteral("QTextBrowser { background: transparent; border: none; "
230+
"font-style: italic; padding-left: %1px; }")
231+
.arg(kThoughtBrowserPadLeft));
223232
m_browser->setOpenExternalLinks(true);
224233
configureBubbleBrowser(m_browser);
225234
// QTextDocument paragraphs carry an implicit ~12px bottom margin even
@@ -535,12 +544,16 @@ void AcpMessageWidget::refitBrowserHeight()
535544
// already have unless we just collapsed to header-only above.
536545
return;
537546
}
547+
int textW = w;
548+
if (m_thoughtHeader) {
549+
textW = qMax(0, w - kThoughtBrowserPadLeft - kThoughtItalicRightSlack);
550+
}
538551
QTextDocument *doc = m_browser->document();
539552
// Force a full layout pass for the current width before measuring —
540553
// QTextDocument under-reports height for freshly-set multi-line text
541554
// until the layout engine has run, which clips an expanded thought down
542555
// to roughly its first line.
543-
const int browserH = qMax(0, static_cast<int>(std::ceil(layoutDocumentHeight(doc, w))));
556+
const int browserH = qMax(0, static_cast<int>(std::ceil(layoutDocumentHeight(doc, textW))));
544557
m_browser->setFixedHeight(browserH);
545558
bubbleH += browserH;
546559
setFixedHeight(bubbleH);

‎tests/test_acp_message_widget_streaming.cpp‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ private slots:
2222
void thought_collapse_after_streaming_done();
2323
void thought_renders_markdown_as_raw_text();
2424
void thought_keeps_body_height_while_hidden();
25+
void thought_body_does_not_overflow_viewport();
2526
void thought_reexpand_while_hidden_restores_body_height();
2627
};
2728

@@ -127,6 +128,33 @@ void TestAcpMessageWidgetStreaming::thought_keeps_body_height_while_hidden()
127128
.arg(collapsedH)));
128129
}
129130

131+
void TestAcpMessageWidgetStreaming::thought_body_does_not_overflow_viewport()
132+
{
133+
// Thought body has stylesheet padding-left and italic glyphs that overhang
134+
// their advance. Layout at the frame content width makes the document a
135+
// few px wider than the viewport: horizontal scroll + clipped last letters.
136+
AcpMessageWidget w(QStringLiteral("thought"));
137+
w.resize(420, 40);
138+
w.setText(QStringLiteral(
139+
"The compile timed out again at 300s while compiling. The crate is huge. "
140+
"I need a longer timeout. 0 disable. the deadline? The bash tool says "
141+
"timeout 0 disables the command deadline. I should set timeout 0."));
142+
143+
auto *browser = w.findChild<QTextBrowser *>();
144+
QVERIFY(browser);
145+
QTextDocument *doc = browser->document();
146+
QVERIFY(doc);
147+
148+
const qreal contentW = w.width() - 8; // thought layout margins 4+4
149+
QVERIFY(doc->textWidth() > 0);
150+
// Must leave pad-left (4) plus italic overhang slack (>=2) inside the
151+
// allocated width. Equality with contentW is the bug this test exists for.
152+
QVERIFY2(doc->textWidth() <= contentW - 4 - 2,
153+
qPrintable(QStringLiteral("textWidth=%1 contentW=%2")
154+
.arg(doc->textWidth())
155+
.arg(contentW)));
156+
}
157+
130158
void TestAcpMessageWidgetStreaming::thought_reexpand_while_hidden_restores_body_height()
131159
{
132160
AcpMessageWidget w(QStringLiteral("thought"));

0 commit comments

Comments
 (0)