Keep each timeline message's action row inside its message - #2257
Draft
brsbl wants to merge 2 commits into
Draft
Conversation
brsbl
force-pushed
the
bb/timeline-actions-row-width-thr_w8uypnhb9x
branch
from
August 21, 2026 20:54
1dda85b to
c2c9e07
Compare
The hover-revealed action row under a timeline message sat in normal flow with no width bound, so it rendered at its natural width regardless of the message above it. A two-letter bubble (51px) carrying three actions (76px) overhung by 25px on desktop; on a touch phone the latest message's inline row overhung a 54px bubble by 46px. Bound the row to the message it belongs to and collapse what does not fit: - Measure the row's slot with a ResizeObserver and keep only the actions that fit; the rest move into a trailing "..." menu. The row is absolutely positioned inside a full-width slot so a wide row can never widen a fit-content message column. - Wrap the user bubble and its row in a sub-column sized by the bubble, so the measured slot is exactly the bubble's width. - On touch, tapping "..." expands the hidden actions in place when the whole set fits the timeline column with room to spare, reaching into the empty gutter beside a narrow bubble. When the column is too tight the anchored popover is used instead, since it scrolls and cannot clip. - Size both menus to their widest label instead of a fixed width, and keep the row revealed while its own menu is open.
The row sat flush with the message's border box, so its outer glyph landed 4px from a bubble's edge — inside the bubble's 12px corner radius, reading as if it hung off the message. On the agent side the same slack pushed the glyph 4px inside the prose edge, indented the other way. Align the outer glyph edge to the message's text edge instead: inset the row by the bubble's padding and border minus the icon's hit-box slack (13px desktop, 11px touch), and pull prose rows out by the slack alone. The slot carries the inset as padding so the measured budget is the text width the row must fit, and the row carries a matching offset because an absolutely positioned child resolves `right` against the padding box.
brsbl
force-pushed
the
bb/timeline-actions-row-width-thr_w8uypnhb9x
branch
2 times, most recently
from
August 22, 2026 01:42
9fa41ca to
d124c9b
Compare
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.
What was wrong
The hover-revealed action row under each timeline message sat in normal flow with no width bound, so it rendered at its natural width regardless of the message it belongs to. A two-letter bubble (51px) carrying three actions (76px) overhung its message by 25px on desktop; on a touch phone the latest message's inline row overhung a 54px bubble by 46px. Because the row was a sibling of the bubble inside a
w-fitcolumn, a wide row could also widen the column itself. The row was misaligned even where it fitted: its outer glyph sat 4px from the bubble's edge, inside the bubble's 12px corner radius, so it read as hanging off the message.What changed
MessageActionBar.tsx,ConversationMessageContent.tsxThe row now fits the message it belongs to. The most icons that fit under the message's width are shown inline, and the rest collapse progressively into a trailing "…" menu. When not even one fits, the row is just the "…". On touch, older messages keep their existing "…"-only footer and the most recent message follows the same rules as desktop.
ResizeObserver;computeMessageActionRowLayoutkeeps the actions that fit and moves the remainder into the menu. The row is absolutely positioned inside a full-width slot, so it can never contribute intrinsic width to a fit-content message column.w-48/w-44(desktop 192px → 150px), capped so a long plugin label wraps rather than running off a narrow viewport.No contract, wire, or CLI surface changed.
Behavior breakpoints
Captured in the real dev app from this checkout — same thread, route and window bounds for each pair, with the product code as the only variable (before = merge base
8201aa762, after = PR head). Numbers are measured from the DOM; "overhang" is how far the controls extend past their message.Desktop 1280px — wide bubble
All three actions fit, so none collapse. Unchanged apart from the alignment.
Desktop 1280px — short bubble (51px)
Before: three actions span 76px, a 25px overhang. After: nothing fits, so the row is the "…" alone.
Desktop — the menu holds what did not fit
Progressive overflow
Eight actions. In a wide column all eight are inline; in a 160px column four fit and the other four move into the menu. (Ladle story — the real thread only has three actions, so it shows either all or none.)
Compact width 600px + mouse — resting, then drawer
Same 25px overhang before. After, the menu opens as the shared bottom drawer at this width.
Touch phone 390px — resting
Older messages keep the "…"-only footer. The most recent message follows the desktop rules: here the latest agent reply spans the column, so its three actions are inline, while the latest user bubble (54px) fits none and shows the "…". Before, that bubble carried 100px of icons — a 46px overhang.
Touch phone — tapping "…" reveals the actions in place
The 358px column fits all three, so they appear in the gutter beside the bubble rather than in a popover.
Touch phone — popover fallback
Eight actions in a 160px column cannot fit, so the popover opens instead, on-screen.
How you verified
pnpm exec turbo run test --filter=@bb/app -- MessageActionBar— 27 passed, 10 new: the layout math at its boundaries (all inline, progressive collapse, everything in the menu), desktop collapse into the "More actions" menu, touch collapse into the popover, reveal-in-place when the column has room plus collapse-on-choose, and the popover fallback when it does not. The new assertions fail against the pre-change component.pnpm exec turbo run test --filter=@bb/app -- "thread/timeline"— 205/207. The two failures (TimelineRowDetails.output-preview,useStickyBottomScroll) are timeout-sensitive under parallel load and pass 3/3 and 2/2 when run alone; neither exercises the action row.pnpm exec turbo run typecheck --filter=@bb/app, eslint and prettier — clean.Not verified: an actually installed PWA.
display-mode: standalonecannot be emulated over CDP; the touch path keys off(pointer: coarse)+(max-width: 767px), and the standalone rule inapp.cssonly sets shell height, which this row does not read.BB-Thread-ID: thr_w8uypnhb9x