Uh oh!
There was an error while loading. Please reload this page.
[Feature] Add Message Scroller component - #447
Conversation
There was a problem hiding this comment.
5 issues found across 21 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
cirdes
left a comment
There was a problem hiding this comment.
@djalmaaraujo, please take a look on cubic-dev comments and ask for review again
djalmaaraujo
commented
Jun 30, 2026
Addressed all 5 cubic findings in
@cirdes ready for another look. |
There was a problem hiding this comment.
2 issues found across 5 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
205355d to
cdec99fComparePort the shadcn Empty component: a centered empty-state surface for when there is no data or content. Parts: Empty, EmptyHeader, EmptyMedia (default/icon variants), EmptyTitle, EmptyDescription, EmptyContent. Translates shadcn's cn-empty-* CSS layer to Tailwind v4 utilities. No JS. Docs page, route, controller, menu, site_files and MCP registry updated.
Port the shadcn Message Scroller: a chat transcript scroller that follows the live edge, anchors new turns near the top, and jumps to the latest message. Built on top of Message (#446) and Bubble (#445). shadcn delegates to a closed React primitive (@shadcn/react); this is a from-scratch Stimulus controller (ruby-ui--message-scroller) — our own code, no external lib: - autoScroll follow-edge: pins to the bottom while the reader is there, releases on wheel/touch/keyboard/scrollbar away, re-engages on jump. - scrollAnchor: settles an appended turn near the top keeping a peek of the previous item (previous_item_peek). - defaultPosition: open at end / start / last-anchor. - preserveOnPrepend: hold the visible row when history loads in above. - Public API for streaming/ActionCable: scrollToEnd/scrollToStart/ scrollToMessage; new rows are picked up via MutationObserver. - rAF-based smooth scrolling (native smooth is unreliable on a contained viewport), honors prefers-reduced-motion. - a11y: content role=log + aria-relevant, button sr-only label, button removed from tab order while inert. Parts: MessageScrollerProvider, MessageScroller, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem, MessageScrollerButton. dependencies.yml: message_scroller depends on Message + Bubble. MCP registry, docs page, route, controller, menu and site_files updated.
- Gate anchored-turn scrolling behind autoScroll/following, so a new turn never yanks a reader who scrolled up to older content (P1). - Scroll button honors data-direction: a start-direction button now jumps to the start instead of the end (renamed action jumpToEnd → jump) (P2). - Guard last-anchor opening position with hasContentTarget; the Stimulus target getter throws rather than returning undefined (P2). - Include the flex row gap in prepend preservation so the visible row no longer drifts down by one gap per history insertion (P2). - Only treat direct content children as transcript rows; markup mutated inside a message (subtree) is handled as streaming, not history (P2). Rebuilt MCP registry.
…y state Add the shadcn-style chat window as the hero example: a Card with an Empty state until the first message, then a scrolling transcript that follows the live edge, plus an input footer. A docs-only Stimulus demo harness (message-scroller-chat) clones server-rendered user/assistant templates on send so the scroller's autoscroll/anchoring is demonstrated live — standing in for a real ActionCable/streaming source. Uses the new Empty component.
cdec99f to
fb46ef4Comparedjalmaaraujo
commented
Jun 30, 2026
Added the faithful shadcn-style chat window as the hero example, addressing the gap raised in review:
This PR now stacks on #448 (Empty) — base retargeted to The earlier "empty state / full chat window out of scope" note no longer applies — both are now included. |
updateButton now derives each button's active state from its own data-direction — an end button activates when away from the bottom, a start button when away from the top — and iterates all button targets. Previously a start-direction button used end logic, so it was inert at the live edge and showed at the wrong end. (cubic P2)
djalmaaraujo
commented
Jun 30, 2026
Fixed in |
Uh oh!
There was an error while loading. Please reload this page.
Bump RubyUI to 1.5.0 (minor: new components since v1.4.0). - gem/lib/ruby_ui.rb → 1.5.0; regenerate gem/ and docs/ Gemfile.lock - docs home hero badge → headline features (Bubble, Message, Empty) - rebuild mcp/data/registry.json Highlights since v1.4.0: - New components: Bubble (#445), Message (#446), Message Scroller (#447), Empty (#448) - Port hover_card & context_menu to Floating UI, drop Popper.js (#438) - Bug fixes: Dialog closed-state + docs controller (#458), DropdownMenu z-index (#440)
Bump RubyUI to 1.5.0 (minor: new components since v1.4.0). - gem/lib/ruby_ui.rb → 1.5.0; regenerate gem/ and docs/ Gemfile.lock - docs home hero badge → headline features (Bubble, Message, Empty) - rebuild mcp/data/registry.json Highlights since v1.4.0: - New components: Bubble (#445), Message (#446), Message Scroller (#447), Empty (#448) - Port hover_card & context_menu to Floating UI, drop Popper.js (#438) - Bug fixes: Dialog closed-state + docs controller (#458), DropdownMenu z-index (#440)
Description
Ports the shadcn Message Scroller to RubyUI — a chat transcript scroller that follows the live edge, anchors new turns near the top, preserves position when older messages load, and jumps to the latest message.
Reference:
apps/v4/registry/bases/radix/ui/message-scroller.tsxOur own code — no external lib
shadcn delegates the behavior to a closed React primitive (
@shadcn/react/message-scroller). This is a from-scratch Stimulus controller (ruby-ui--message-scroller) — the first JS component in this stack — replicating the documented behaviors in vanilla JS:previous_item_peek, default 64).end/start/last-anchor.scrollToEnd,scrollToStart,scrollToMessage. New rows are picked up automatically viaMutationObserver(childList + characterData), so streamed tokens keep the view pinned.scrollTo({ behavior: "smooth" })is unreliable on a contained viewport, so the controller animatesscrollTopitself; honorsprefers-reduced-motion.role="log"+aria-relevant; the button has an sr-only label and is removed from the tab order while inert.Parts
MessageScrollerProvider(owns scroll state) ·MessageScroller(frame) ·MessageScrollerViewport·MessageScrollerContent·MessageScrollerItem(scroll_anchor:,message_id:) ·MessageScrollerButton(direction:).Reuses Message + Bubble;
dependencies.ymldeclares both.Out of scope (vs shadcn)
The full primitive also does row-virtualization and exposes visibility/animation hooks. This port keeps the documented scroll behaviors and drops
content-visibilityvirtualization, which madescrollHeightunstable and fought reliable programmatic scroll-to-end. The demo is a self-contained thread (noai-sdk); it's wired so a future ActionCable/streaming source can drive it through the public API.Testing
cd gem && bundle exec rake→ tests + StandardRB green./docs/message_scroller: opens at the live edge, scroll up reveals the jump-to-latest button, clicking it smooth-scrolls back to the bottom and hides the button. Scroll-command buttons drive the viewport from outside the frame.Screenshots
Add before/after of
/docs/message_scroller.Summary by cubic
Adds a Message Scroller and an Empty state. The scroller follows the live edge, anchors new turns, preserves position when history loads, and provides a jump-to-latest button. Implemented with a Stimulus controller, no external libraries.
New Features
MutationObserver.message-scroller-chatcontroller; route/nav/tests/registry updated; generatordependencies.ymllistsMessageandBubble.jump; direction-aware button visibility (start vs end) with multiple targets; guard last-anchor when content is missing; include row gap in prepend preservation; treat only direct content children as rows (subtree mutations count as streaming).Migration
MessageandBubble.Written for commit 7e4e3ed. Summary will update on new commits.