Bubble
Seven chat surfaces on one axis, from a solid primary send to no surface at all, with the reaction pill already drawn into every one of them and switched off.
Every variant carries the reaction pill already, switched off. Showing one does not move the bubble: the pill hangs off the bottom edge with a 3px base.card ring so it reads as sitting on top of whatever is behind it. That is why Variant is the only axis.
It is the one surface the kit had no colour for: lighter than base.secondary, heavier than base.muted. base.bubble-tinted was added for it, #E8E8E8 in light and #2E2E2E in dark, so it themes with everything else rather than sitting on a literal.
No fill, no padding, no radius: 280 × 46 against everyone else's 66. It is for text that belongs in the thread without being a message, which in the usage frame is the timestamp under a failed send.
Bubble Group, and where the sides come from
420 wide, a column with 8 of gap, holding three Bubble instances. It has no axes: which side a message sits on is set per bubble inside the group rather than by a property on the group, because a real thread alternates and a two-value axis could not express that.
A thread, a reaction, and a send that failed
620 by 803, three sections. The last one is the reason Ghost exists: a Destructive bubble for the error, with the timestamp under it as a Ghost bubble rather than as loose text.
Two components, seven variants
Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.
Using the Bubble
Because the other things the source exposes do not multiply the matrix. Reactions are a layer that every variant already carries, hidden, and revealing one does not move the bubble. Which side a message sits on belongs to the row it is in, not to the bubble. So seven variants covers it, and nothing has to be drawn twice.
A colour the kit did not have. Tinted sits between base.secondary and base.muted, and neither of them was the right value, so a token was added rather than a literal typed in: #E8E8E8 in light, #2E2E2E in dark. It themes with everything else, which a hex would not.
It hangs off the bottom edge with a 3px ring in base.card, so it reads as sitting on top of whatever is behind rather than as another row inside. The bubble stays 66 tall whether the pill is showing or not, which is why Reactions is a hidden layer instead of an axis.
For text that belongs in the thread without being a message. No fill, no padding, no radius, 46 tall against everyone else's 66. In the usage frame it is the timestamp under a failed send, which is exactly the case the source describes as unframed assistant text.
From the bubble inside the group, not from a property on the group. A real thread alternates sides, so a two-value axis on Bubble Group could not express it; each bubble is aligned where it belongs and the group only handles the 8px rhythm between them.