Skip to content

fix(chat): nest the reply quote concentrically in its bubble - #735

Merged
bmc08gt merged 4 commits into
mainfrom
fix/chat-quote-concentric-corners
Sep 8, 2026
Merged

fix(chat): nest the reply quote concentrically in its bubble#735
bmc08gt merged 4 commits into
mainfrom
fix/chat-quote-concentric-corners

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

The quote panel inside a reply's bubble had an 8pt corner sitting in the bubble's 12, with a 12pt gap down the sides and 9 at the top — neither arc parallel to the other, and two different gaps to read it against.

The radius now comes from a single surround: the panel is inset 9 on the top, leading and trailing edges (the bubble's own vertical margin) and takes the bubble's radius less that gap, so the distance between the two corners holds constant through the turn. The gap under the panel is the same 9, which puts the quote, the body and the bubble's edges on one rhythm.

ChatBubbleView and LinkableBubbleView both pinned the panel's top with a literal 9 and its sides with the constant. Both now read the constant on all three sides, so the surround cannot drift away from the radius derived from it.

The second commit is unrelated to the change and was needed to run the tests at all: let marker = try #require(marker(in: bubble)) in ChatBubbleViewTests reads as a call to the helper, but the #require expansion resolves marker to the UILabel the line is declaring, and FlipcashTests failed to compile with 17 copies of "cannot call value of non-function type 'UILabel'". The helper is now editedMarker(in:).

The last two commits fix a bug the change surfaced, in two halves. Both come from the same place: the panel is pinned to both of the bubble's sides, so whatever it demands becomes the bubble's floor, and hiding it does not stop that — isHidden only skips drawing.

Bubbles holding the same short message first came back at three different widths, because a recycled cell kept the author name and snippet of the reply it last held. A recycled bubble measured 155pt wider than a fresh one holding the same "hi". The panel is now emptied when the message has no quote.

Emptied, it still demanded 24pt — the rule, its two gutters and the detail row's spacing — which with the bubble's 18pt of insets put a 42pt floor under every bubble. "." rendered at 42 against a natural 28.3. The panel now collapses in both axes rather than height alone, its internal horizontal chain sits at .required - 1 so the zero-width constraint can break it, and the trailing pin is active only alongside a quote: against a collapsed panel the same equality pulls the bubble's edge inward and squeezes the body to nothing.

The quote panel's 8pt corner sat inside the bubble's 12 with a 12pt gap
down the sides and 9 at the top: neither arc parallel to the other, and
two different gaps to read it against.

Derive the radius from a single surround instead. The panel is inset 9 on
the top, leading and trailing edges — the bubble's own vertical margin —
and takes the bubble's radius less that gap, so the distance between the
two corners stays constant through the turn. The gap under the panel
becomes the same 9, putting the quote, the body and the bubble's edges on
one rhythm.

Both bubbles pinned the panel's top with a literal 9 and its sides with
the constant; both now read the constant on all three sides, so the
surround can't drift away from the radius derived from it.
`let marker = try #require(marker(in: bubble))` reads as a call to the
helper, but the #require expansion resolves `marker` to the UILabel the
line is declaring. FlipcashTests failed to compile with 17 copies of
"cannot call value of non-function type 'UILabel'", which took the whole
target down with it.

Rename the helper to editedMarker(in:).
@bmc08gt bmc08gt self-assigned this Sep 8, 2026
Bubbles holding the same short message came back at three different
widths. The cell recycles its bubble, and a bubble with no quote hides
the panel and pins it to zero height — but `isHidden` only skips drawing.
The panel is still pinned to both of the bubble's sides, so the author
name and snippet left over from the reply that cell last held went on
demanding their width, and the bubble stayed as wide as that quote. A
recycled bubble measured 155pt wider than a fresh one holding the same
"hi".

Empty the panel when the message has no quote, so there is no text left
to ask for the space.
Every bubble had a 42pt floor under its width, so a one- or two-character
message came out as wide as a short phrase. The quote panel is pinned to both
of the bubble's sides, and the rule, its two gutters and the detail row's
spacing add up to 24pt the panel demands with nothing in it; the bubble's own
18pt of insets make the rest. Collapsing the panel's height when there is no
quote left that width in place.

Collapse both axes instead, and demote the panel's internal horizontal chain to
`.required - 1` so the zero-width constraint can break it. The trailing pin goes
with it: an equality against a collapsed panel pulls the bubble's trailing edge
inward rather than outward, squeezing the body to nothing, so it is active only
alongside a quote.

`noQuote_hugsItsText` measures ".", "hi" and "Hello there" against the text's own
width plus the bubble's padding, and `reuse_hugsItsOwnText` does the same for a
recycled bubble — both failed by the 14pt the floor added.
@bmc08gt
bmc08gt merged commit f03e609 into main Sep 8, 2026
1 check passed
bmc08gt added a commit to code-payments/code-android-app that referenced this pull request Sep 8, 2026
The citation panel inside a reply drew an 8dp corner inside the bubble's 12,
with a 16dp gap down its sides, 10 at the top and 6 under it. Four numbers,
none derived from the others, so neither arc ran parallel to the other and
there were three different gaps to read the panel against.

Derive the panel from the bubble instead. `BubbleDefaults` names the bubble's
own geometry, and `surroundInset` is one gap used on the panel's top, leading
and trailing edges and again between the panel and the body beneath it, so the
corner has a single surround to be concentric with: the bubble's radius less
that gap.

The bubble hands a reply the surround as its horizontal padding and the body
puts the difference back, which is how the panel reaches past the text on both
sides. A bubble with no quote passes the bubble's own padding, so the
difference is zero and nothing about it moves.

The panel also stretches to the bubble's width when it is the narrower of the
two. A `Column` left a short quote hanging inside a wider reply, and the panel
is a filled surface, so the slack read as a notch cut out of the bubble.
`fillMaxWidth` fills the incoming maximum rather than the sibling's width, so
`QuotedBody` measures the body first and makes its width the panel's floor.
A quote longer than the reply still keeps its own width and carries it out to
the bubble.

Every measurement that has a token now reads one. `CodeTheme.shapes.medium`
and `.tiny` supply the bubble's two radii through a new
`CornerBasedShape.cornerRadius()`, and the paddings come off
`CodeTheme.dimens.staticGrid`. That grid is 5pt and has no 16dp step, so the
bubble's horizontal padding moves to 15. The panel's accent width, flag size
and alphas stay as literals; they came from iOS and have no token to come from.

iOS landed the same surround in code-payments/code-ios-app#735. One difference
is deliberate: the radius is floored at the bubble's flattened corner, because
iOS's unfloored 3pt is a continuous curve and the same number as a circular arc
draws a hard rectangle inside a rounded one.
bmc08gt added a commit to code-payments/code-android-app that referenced this pull request Sep 8, 2026
The citation panel inside a reply drew an 8dp corner inside the bubble's 12,
with a 16dp gap down its sides, 10 at the top and 6 under it. Four numbers,
none derived from the others, so neither arc ran parallel to the other and
there were three different gaps to read the panel against.

Derive the panel from the bubble instead. `BubbleDefaults` names the bubble's
own geometry, and `surroundInset` is one gap used on the panel's top, leading
and trailing edges and again between the panel and the body beneath it, so the
corner has a single surround to be concentric with: the bubble's radius less
that gap.

The bubble hands a reply the surround as its horizontal padding and the body
puts the difference back, which is how the panel reaches past the text on both
sides. A bubble with no quote passes the bubble's own padding, so the
difference is zero and nothing about it moves.

The panel also stretches to the bubble's width when it is the narrower of the
two. A `Column` left a short quote hanging inside a wider reply, and the panel
is a filled surface, so the slack read as a notch cut out of the bubble.
`fillMaxWidth` fills the incoming maximum rather than the sibling's width, so
`QuotedBody` measures the body first and makes its width the panel's floor.
A quote longer than the reply still keeps its own width and carries it out to
the bubble.

Every measurement that has a token now reads one. `CodeTheme.shapes.medium`
and `.tiny` supply the bubble's two radii through a new
`CornerBasedShape.cornerRadius()`, and the paddings come off
`CodeTheme.dimens.staticGrid`. That grid is 5pt and has no 16dp step, so the
bubble's horizontal padding moves to 15. The panel's accent width, flag size
and alphas stay as literals; they came from iOS and have no token to come from.

iOS landed the same surround in code-payments/code-ios-app#735. One difference
is deliberate: the radius is floored at the bubble's flattened corner, because
iOS's unfloored 3pt is a continuous curve and the same number as a circular arc
draws a hard rectangle inside a rounded one.
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