[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
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
|
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
205355d to
cdec99f
Compare
Port 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
fb46ef4
Compare
|
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)
|
Fixed in |
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.