Skip to content

Keep each timeline message's action row inside its message - #2257

Draft
brsbl wants to merge 2 commits into
mainfrom
bb/timeline-actions-row-width-thr_w8uypnhb9x
Draft

Keep each timeline message's action row inside its message#2257
brsbl wants to merge 2 commits into
mainfrom
bb/timeline-actions-row-width-thr_w8uypnhb9x

Conversation

@brsbl

@brsbl brsbl commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

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-fit column, 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.tsx

The 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.

  • The row's slot is measured with a ResizeObserver; computeMessageActionRowLayout keeps 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.
  • The user bubble and its row sit in a sub-column sized by the bubble, so the measured budget is the bubble's width.
  • Aligned to the message text. The row is inset by the bubble's padding and border minus the icon's hit-box slack (13px desktop, 11px touch), and prose rows are pulled out by the slack alone, so the outer glyph edge lands exactly on the text edge on both sides.
  • The "…" sits 4px from the last inline action rather than the row's 8px gap, so it reads as the row's continuation, and the row stays revealed while its own menu is open.
  • On touch, tapping "…" reveals the actions in place when they fit the timeline column with a 16px margin, reaching into the empty gutter beside a narrow bubble; when the column is too tight the anchored popover opens instead, since it scrolls and cannot clip.
  • Menus size to their widest label instead of a fixed 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.

Before After

Desktop 1280px — short bubble (51px)

Before: three actions span 76px, a 25px overhang. After: nothing fits, so the row is the "…" alone.

Before After

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.

Before After Menu open (after)

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.

Before After

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.
  • Manual: every breakpoint above driven in the real dev app over CDP, with a DOM audit asserting each row's control bounds against its message (0px overhang everywhere) and its glyph against the message text edge (0.0px delta on both sides, both breakpoints).

Not verified: an actually installed PWA. display-mode: standalone cannot be emulated over CDP; the touch path keys off (pointer: coarse) + (max-width: 767px), and the standalone rule in app.css only sets shell height, which this row does not read.

BB-Thread-ID: thr_w8uypnhb9x

AGENT GENERATED: by Claude Opus 5

@brsbl
brsbl force-pushed the bb/timeline-actions-row-width-thr_w8uypnhb9x branch from 1dda85b to c2c9e07 Compare August 21, 2026 20:54
brsbl added 2 commits August 21, 2026 18:08
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
brsbl force-pushed the bb/timeline-actions-row-width-thr_w8uypnhb9x branch 2 times, most recently from 9fa41ca to d124c9b Compare August 22, 2026 01:42
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