Skip to content

fix(web): the fade follows measured overflow, so text that fits is not dimmed forever#554

Merged
gbasin merged 1 commit into
masterfrom
fix/clamp-fade
Jul 16, 2026
Merged

fix(web): the fade follows measured overflow, so text that fits is not dimmed forever#554
gbasin merged 1 commit into
masterfrom
fix/clamp-fade

Conversation

@gbasin

@gbasin gbasin commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Found while browser-verifying #545 after it landed.

The bug

#545 made the toggle depend on measured overflow instead of the length heuristic — good — but left the fade on the collapsed state alone:

const clamped    = canClamp && !expanded;   // fade rode on this
const showToggle = canClamp && overflows;   // toggle rode on this

clamped applies collapsedClassName, and for MessageText that string carried the fade mask. So a message long enough to trip COLLAPSE_LINE_THRESHOLD (16 lines) or COLLAPSE_CHAR_THRESHOLD (1800 chars) but short enough to fit inside max-h-80 renders with the bottom of its box faded to transparent and no toggle to clear it. The text is simply dimmed, permanently.

Before #545 the toggle was gated on the heuristic too, so that message faded but could be expanded — the fade was escapable. #545's note that this case "no longer offers a toggle that does nothing" understates it: the toggle was doing something visible. It removed the fade.

Easy to hit with markdown that renders more compactly than its source — blank-line-padded text is the obvious one.

The fix

The constraint has to stay applied while collapsed, because overflow is measured through it: drop max-h-80 and scrollHeight === clientHeight, so nothing ever reports overflowing and the block never clamps at all. Gating collapsedClassName on overflows would be circular.

So the two concerns split:

  • collapsedClassName — the size constraint, applied whenever collapsed (needed to measure)
  • overflowingClassName — the hint that there is more to see, applied only when it actually overflows

MessageRow's clamp is unaffected: line-clamp-3 is a no-op when the content fits, so it never needed the split.

Verification

Real browser (Chromium, built + preview), reading computed style — jsdom has no layout and cannot see a mask:

case overflows Show more mask-image before after
trips heuristic, fits (76px in a 320px box) false 0 linear-gradient(rgb(0,0,0) 70%, rgba(0,0,0,0)) none
genuinely overflows (933px in 320px) true 1 present present ✓
below threshold 0 not clamped not clamped ✓

Also confirmed #545's own win is intact — "Show more" still grows the reply (111px → 725px → 111px), nowhere near the #538 shrink (360 → 87).

  • pnpm lint ✓ · pnpm -r typecheck ✓ · 761 web unit tests ✓
  • e2e cluster-steer-interleave + session-pane-ux + spine-aside: 7 passed
  • Unit tests added for both directions (the existing suite stubs scrollHeight/clientHeight + ResizeObserver, so the class-level behaviour is testable even though the visual is not)

…t dimmed forever

#545 made the Show more toggle depend on MEASURED overflow instead of the
length heuristic, but left the fade on the collapsed state alone:

  clamped     = canClamp && !expanded          // fade rode on this
  showToggle  = canClamp && overflows          // toggle rode on this

A message long enough to trip COLLAPSE_LINE_THRESHOLD / CHAR_THRESHOLD but
short enough to fit inside max-h-80 therefore rendered with the bottom of
its box faded to transparent and NO toggle to clear it. Before #545 the
toggle was gated on the heuristic too, so the same message faded but could
be expanded; the fade was escapable. #545's note that this case merely
'no longer offers a toggle that does nothing' understates it -- the toggle
was doing something visible: it removed the fade.

The constraint has to stay applied while collapsed, because overflow is
measured THROUGH it (drop max-h and scrollHeight === clientHeight, so
nothing ever reports overflowing and the block never clamps). So the fix
splits the two: collapsedClassName keeps the constraint, and a new
overflowingClassName carries the hint, applied only when the content
actually overflows.

Verified in a real browser (jsdom has no layout and cannot see a mask):
a fitting message reports mask-image: none where it previously reported
linear-gradient(...), while a genuinely overflowing message keeps both its
fade and its toggle.
@gbasin
gbasin added this pull request to the merge queue Jul 16, 2026
Merged via the queue into master with commit 7488a98 Jul 16, 2026
16 checks passed
@gbasin
gbasin deleted the fix/clamp-fade branch July 16, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant