Skip to content

fix(desktop): square the ghost answer bubble under paint containment - #4923

Merged
Astro-Han merged 2 commits into
mainfrom
fix/4898-boundary-corner-clip
Sep 6, 2026
Merged

fix(desktop): square the ghost answer bubble under paint containment#4923
Astro-Han merged 2 commits into
mainfrom
fix/4898-boundary-corner-clip

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The assistant answer bubble is an Astryx ghost ChatMessageBubble: no fill, no border, and the 28px --radius-chat it inherits paints nothing. #4259 made that bubble a content-visibility boundary, and paint containment clips to the rounded padding box, so the invisible corners became clip arcs. In the app (Electron 43.4.1, Chromium 150) the arc shaves the top-left of the first glyph on the first line and the bottom-left of the last line: 环 renders as 不 with a stub, and a list's 3. loses its 3.

#4906 added overflow-clip-margin: 2px against the same symptom. That only pushes the arc out by 2px; in Electron the glyph still loses its corner. The pixel check that passed there ran in Playwright's headless Chromium, where the rounded clip does not reproduce, so Storybook smoke cannot see this either way.

The fix squares the ghost bubble (border-radius: 0), which is legal under DESIGN.md's Full-Bleed Rule (width="100%", flush on both sides) and changes no pixel of its own. The 2px margin goes: with the radius gone it makes no measurable difference at 0px or 2px on CJK (环, 第) and Latin (Jfj PQ) samples in Electron. DESIGN.md's chat rung row records the exception, and the WideAssistantProse play walks from the prose up to the transcript scroller and requires every clipping ancestor (content-visibility: auto, paint containment, non-visible overflow) to be square or to pad its content past its corner radius. Headless Chromium cannot show the symptom, so the story pins the geometry that produces it, for any ancestor rather than only this bubble.

Fixes #4898

Verification

Measured in the real app via CDP, Electron 43.4.1 / Chromium 150, session with a multi-block Turn, 4x crops of the first glyph of the second answer:

  • main bd3646b34: top-left of 环 shaved; border-radius: 0 alone restores it; overflow-clip-margin: 8px also restores it (arc pushed clear), 2px does not.
  • this branch: intact, light and dark; overflow-clip-margin 0px vs 2px: identical.
  • Playwright Chromium 148 (Storybook) does not reproduce the rounded clip on main, which is why fix(desktop): preserve glyphs at transcript clip edges #4906's pixel_diff=0 held.

glyph light

glyph dark

app light

app dark

  • npm --workspace @maka/desktop run smoke:storybook — passed (310 stories, 337 theme renders)
  • The WideAssistantProse play on main's CSS fails with div.astryx-chat-message-bubble … clips with a 28px corner but only 0px of padding
  • npm --workspace @maka/desktop run typecheck — passed
  • npm run format / npm run lint — clean
  • Not run: E2E (no Electron main-process or scroll-authority change)

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code diagnosed the rounded-clip cause with CDP experiments in the running app, wrote the CSS, story and DESIGN.md change, and produced the comparison images under maintainer direction.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

The assistant answer bubble is an Astryx ghost ChatMessageBubble: no fill,
no border, and the 28px --radius-chat it inherits paints nothing. #4259 made
that bubble a content-visibility boundary, and paint containment clips to
the rounded padding box, so the invisible corners became clip arcs. In the
app's Electron 43 (Chromium 150) the arc shaves the top-left of the first
glyph of the first line and the bottom-left of the last line: 环 renders as
不 with a stub, a list's "3." loses its 3.

#4906 added overflow-clip-margin: 2px against the same symptom. That only
pushes the arc out by 2px; in Electron the glyph still loses its corner, and
the pixel check that passed there ran in Playwright's headless Chromium,
where the rounded clip does not reproduce. The margin does nothing once the
radius is gone, measured at 0px and 2px on CJK and Latin samples, so it goes.

border-radius: 0 on the ghost bubble is legal under DESIGN.md's Full-Bleed
Rule (width 100%, flush both sides) and changes no pixel of its own: the
bubble has nothing to round. DESIGN.md's chat rung row now says so. The
WideAssistantProse play asserts the boundary is square instead of asserting
a clip margin.

Fixes #4898

Generated-by: Claude Code
@github-actions github-actions Bot added the effort/S Under 100 readable lines label Sep 6, 2026
@Astro-Han
Astro-Han marked this pull request as ready for review September 6, 2026 11:51

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at exact head acca82b9. Reviewed under the layered order — design, then function, then complexity, then tests — and it passes each one. Only [P3] notes remain.

Design

The problem is real and reported by someone other than the author: #4898 is filed by Sun-GLiang against a local Desktop build, with a reproduction. This PR then adds the part the report could not: why the earlier attempt did not work, and why its check passed anyway. #4906's overflow-clip-margin: 2px only pushes the clip arc out by 2px, and its pixel_diff=0 held because Playwright's headless Chromium 148 does not reproduce the rounded clip at all. Measuring in the real app — Electron 43.4.1 / Chromium 150, 4× CDP crops — is what separates "the check is green" from "the bug is gone".

Squaring the bubble is a design-system change, and it is settled in the same change rather than assumed: DESIGN.md's chat rung now records the exception and its reason. The bubble is an Astryx ghost ChatMessageBubble — no fill, no border — so the inherited 28px radius paints nothing, and width="100%" flush on both sides puts it under the Full-Bleed Rule. Removing a radius that renders no pixel is not a visual change; it only stops the paint-containment box from clipping to an arc.

Function

The fix is in production CSS, and the evidence is from the real renderer rather than a fixture: on bd3646b34 the top-left of 环 is shaved, border-radius: 0 alone restores it, overflow-clip-margin: 8px also restores it by pushing the arc clear, and 2px does not. On this branch the glyph is intact in both schemes and 0px vs 2px are identical.

Complexity

It fixes at the cause and removes the workaround rather than layering on it: overflow-clip-margin: 2px goes, net −2 lines. With the radius gone the margin makes no measurable difference on CJK (环, 第) or Latin (Jfj PQ) samples, so it is dead weight, not a second guard. That lowers the entropy here rather than raising it.

Tests

The WideAssistantProse assertion moved from overflowClipMargin >= 2 to borderTopLeftRadius === '0px', and it runs against the production element — paragraph.closest('[data-maka-transcript-boundary]'), with contentVisibility === 'auto' asserted alongside so the boundary is still the thing being measured. On main that boundary inherits 28px, so the new assertion fails on the old behaviour. It discriminates.

[P3] What the guard can and cannot see

Worth stating because the PR is candid about it and a later reader should not over-trust the green: the story pins the mechanism, not the symptom. It proves the boundary is square; it cannot prove a glyph is unclipped, because headless Chromium does not reproduce the rounded clip in the first place — which is exactly how #4906 passed while the bug survived.

So the regression guard here is "the radius stays 0", and the symptom check lives in the CDP measurement recorded in this PR, which CI does not repeat. That is the right trade given the tooling, and no change is being asked for. It is worth knowing that if this ever regresses in the other direction — the clip returning through some other rounded ancestor — this assertion would not catch it.

简体中文

在 exact head acca82b9 上批准。按分层顺序评审 —— 设计、功能、复杂度、测试 —— 逐层通过,只剩 [P3] 注记。

设计层

问题真实,且由作者以外的人报告:#4898Sun-GLiang 针对本地 Desktop 构建提出,带复现。本 PR 补上了报告给不出的那部分:先前那次尝试为什么没修好,以及它的检查为什么仍然通过。 #4906overflow-clip-margin: 2px 只把裁剪弧外推 2px,而它的 pixel_diff=0 之所以成立,是因为 Playwright 的 headless Chromium 148 根本不复现这个圆角裁剪。在真实应用里测量(Electron 43.4.1 / Chromium 150,4× CDP 裁切)才是「检查是绿的」与「缺陷没了」之间的区别。

把气泡改方是设计系统层面的改动,而它在同一次改动里定案而非默认通过:DESIGN.md 的 chat 档位现在记录了这个例外及其理由。该气泡是 Astryx 的 ghost ChatMessageBubble —— 无填充、无边框 —— 所以继承来的 28px 圆角不绘制任何像素,而 width="100%" 两侧齐平使它落在 Full-Bleed 规则之下。移除一个不绘制任何像素的圆角不是视觉改动,它只是让绘制包含盒不再按弧线裁剪。

功能层

修复落在生产 CSS 上,证据来自真实渲染器而非夹具:在 bd3646b34 上「环」的左上被削;单独 border-radius: 0 即可恢复;overflow-clip-margin: 8px 也能恢复(把弧推开),2px 不能。本分支上明暗两色下字形完整,0px 与 2px 无差别。

复杂度层

修在成因处,并且移除了那个权宜之计而不是叠加:overflow-clip-margin: 2px 被删,净 −2 行。圆角去掉之后,该 margin 在 CJK(环、第)与拉丁(Jfj PQ)样本上都测不出差别,所以它是死重,不是第二道守卫。这里是降熵而非增熵。

测试层

WideAssistantProse 的断言从 overflowClipMargin >= 2 改为 borderTopLeftRadius === '0px',而且跑在生产元素上 —— paragraph.closest('[data-maka-transcript-boundary]'),并同时断言 contentVisibility === 'auto',以确保被测的仍是那个边界。main 上该边界继承 28px,所以新断言在旧行为上会失败。它具备区分力。

[P3] 这道守卫能看见什么、看不见什么

值得写明,因为 PR 自己很坦白,而后来的读者不该过度信任这个绿:该 story 钉住的是机制,不是症状。 它证明边界是方的;它无法证明字形没有被裁,因为 headless Chromium 根本不复现这个圆角裁剪 —— 而这正是 #4906 通过了而缺陷仍然存活的原因。

所以这里的回归守卫是「圆角保持为 0」,症状层面的检查存在于本 PR 记录的 CDP 测量中,而 CI 不会重复它。就现有工具而言这是正确的取舍,此处不要求改动;但值得知道:若将来它以另一种方式回归 —— 裁剪经由别的带圆角的祖先回来 —— 这条断言不会捕捉到。


Automated review notice: This comment was posted by an automated review agent operated by jackwener. It is not an independent human review and does not replace one.

@Sun-GLiang Sun-GLiang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head acca82b9. Approve — no blocking findings.

Standards

No findings. The assistant bubble is variant="ghost" and width="100%", so border-radius: 0 complies with the DESIGN.md Full-Bleed Rule. The CSS, Storybook assertion, and design contract change form one cohesive fix; no scope creep or material code smell was found. git diff --check, Storybook TypeScript checking, and Biome checks passed.

Spec

No actionable findings. Independent Electron 43.4.1 / Chromium 150 / DPR 2 A/B verification reproduced the diagnosed rounded paint-containment clip:

  • 28px radius + 2px overflow-clip-margin versus 0px radius + 0px changed 66 physical pixels, localized to the first-line top-left and last-line bottom-left glyph corners.
  • The PR default rendering was pixel-identical to a forced square boundary (pixel_diff=0).
  • With the square boundary, overflow-clip-margin: 0px and 2px were pixel-identical for CJK and italic Jfj samples.

This confirms that removing the invisible 28px radius fixes the actual Electron rendering issue, while the removed 2px paint apron is redundant once the boundary is square.

Non-blocking note

The WideAssistantProse story guards the diagnosed mechanism (content-visibility: auto plus a square boundary), not the rendered-pixel symptom. Headless Storybook Chromium does not reproduce this Electron-only clip, so a future regression through a different rounded ancestor would require an Electron-level visual check to catch automatically. No change is requested for this PR.

Summary: Standards 0 findings; Spec 0 findings; one non-blocking regression-coverage note.


Automated review notice: this review was prepared and posted by Codex on behalf of Sun-GLiang; it does not replace independent human judgment.

The WideAssistantProse play asserted only the boundary bubble's own radius.
Headless Chromium does not reproduce the rounded paint clip, so the story can
never see the symptom; what it can pin is the geometry that produces it. Walk
from the paragraph to the transcript scroller and require every ancestor that
clips (content-visibility auto, paint containment, non-visible overflow) to be
square or to pad its content past its corner radius. On main's CSS this fails
naming the bubble: 28px corner, 0px padding.

Generated-by: Claude Code
@Astro-Han
Astro-Han merged commit 3697e63 into main Sep 6, 2026
1 check passed
@Astro-Han
Astro-Han deleted the fix/4898-boundary-corner-clip branch September 6, 2026 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/S Under 100 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(desktop): assistant response glyphs are clipped at the left edge

3 participants