Every chat bubble in hq.training's native screens renders one word per line.
"Went 4.3 miles" comes out three lines tall and about as wide as the word "miles" — in the conversation screen and in a session's comments, both of which draw an ordinary bubble:
<View class="flex-row" :class="row.mine ? 'justify-end' : 'justify-start'">
<View class="px-3.5 py-2 max-w-[78%] rounded-[18px]" :class="…">
<Text class="text-[17px]" :text="row.body" />
</View>
</View>The bubble has no width of its own, so its width comes from the Text inside it. The engine answers with the Text's minimum intrinsic width — the longest single word — rather than its maximum — the whole string on one line, which is what a shrink-to-fit box should take before wrapping.
It is not the markup. I first assumed the app had cross-sized the bubble with items-end, rewrote it as the main-axis row above, rebuilt, and got exactly the same three lines. The conversation screen already used the row form and has the same bug. Both are below.
Reproduction, hq.training at 00e3b7b (iPhone 17 Pro simulator, iOS 26, Craft 0.0.158):
- open a conversation, send
Went 4.3 miles - the bubble is ~65pt wide and three lines tall
Expected: the bubble takes the text's full width (~95pt here) and stays one line, wrapping only when the text would pass max-width.
What the browser does with the same intent: an inline-block bubble in the web version of the same screen measures 129×39 at 375px — one line. That is shrink-to-fit sizing against max-content, clamped by the available width.
Where it bites: any View whose width is decided by a Text child — bubbles above all, but also pills, badges, chips and any auto-width label. A node with a definite width is unaffected, which is why most of the app looks right and only the bubbles are wrong.
Guessing at the fix: a flex item's base size on the main axis should be the text's max-content width, with max-width clamping it and wrapping happening at the resulting width. Min-content is the right answer only for the minimum size, which is what stops a flex item shrinking past a word — not what sets its base.