fix(chat): nest the reply quote concentrically in its bubble - #735
Merged
Conversation
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:).
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
ChatBubbleViewandLinkableBubbleViewboth pinned the panel's top with a literal9and 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))inChatBubbleViewTestsreads as a call to the helper, but the#requireexpansion resolvesmarkerto theUILabelthe line is declaring, andFlipcashTestsfailed to compile with 17 copies of "cannot call value of non-function type 'UILabel'". The helper is noweditedMarker(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 —
isHiddenonly 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 - 1so 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.