Chat Modal
RapBotRito’s chat lives inside the ChatBot modal rather than taking over the full viewport. It has to float above other docs content, let you scroll past it, and still feel like a neon performance venue with inline tool chips and a music player strapped to the bottom.
Because this UI is not a fixed, full-screen overlay, it relies on flexible heights (80vh, mobile overrides, and the textareaExpanded signal) plus scroll-pinning logic in ChatMessages.tsx to keep the transcript readable even while the on-page document continues to scroll underneath.
What this page covers
- Layout and stacking via
ChatContainer.tsxandChatWithPlayer.module.css - Brand-forward header treatment (
ChatHeader.tsx) - Transcript scaffolding in
ChatMessages.tsxand its CSS module MessageContent.tsx’s segment parser hook-up (inline renderers will get their own doc)- The adaptive composer defined in
ChatForm.tsx - Packages and stores that wire AI streaming, wallet identity, and tool chips into the modal
Layout & Stacking (ChatContainer.tsx, ChatWithPlayer.module.css)
ChatContainer wraps everything inside a rounded, glowing frame with a translucent image overlay (/images/rito/rito-thinker.jpg). The CSS module:
- Keeps the modal at
width: 100%with an800pxcap so it can fit both desktop and mobile breakpoints. - Sets
height: 80vhwithoverflow: hidden(and95vh/max-height: 80vhon screens under729px) so the chat feels anchored even though the page can scroll past it. - Animates a soft glow via
@keyframes containerGlowPulseand injects theblueglowutility class for more neon ambience. - Uses
.container > *:not(.imageOverlay):not([data-modal-overlay])to ensure only the decorative overlay sits atz-index: 1—all interactive children remain atz-index: 2and stay clickable.
ChatWithPlayer.module.css adds the structure that makes the modal feel like a self-contained device:
.stackis a centered column with a12pxgap, so the chat body andMusicBarstack naturally even when the overall docs page scrolls..fullWidthRowcentersChatContaineritself, keeping the border radius symmetrical regardless of viewport width.- Because the player sits outside the container, the modal always reserves space for audio controls—see the placeholder card below for the upcoming audio deep-dive.
Header & Identity (ChatHeader.tsx)
The header keeps the content approachable and on-brand:
ChatHeadersimply renders🤖 RapBotRito 🎤, but the CSS cranks the font to3.5rem, paints it with--accent-color, and adds a heavy drop shadow so it reads clearly over the translucent background.- Media queries drop the size to
2.5remunder500px, preventing the header from crowding the transcript on mobile screens.
Transcript Scaffolding (ChatMessages/*.tsx & .module.css)
ChatMessages.tsx is a forwardRef component that exposes jumpToBottomAndPin, jumpToLatestUserMessageBottomAndPin, and pinToCurrent so the parent ChatBot can keep the latest user turn in view after every submission.
Message bubble composition
MessageBubbledecides betweenAssistantHeaderand the Wagmi-poweredUserHeaderWithWagmi(wrapped inWagmiBoundaryfor SSR safety) so each role is visually distinct.- Assistant messages call
splitPartsAtAnchorto separate streamed text intobeforeandafterchunks relative to the current tool anchor.ToolActivityRowdrops inline chips in the middle—those chips will get their own doc, but this component guarantees they expand inside the bubble instead of drifting below it. - Both roles render
<MessageContent>for text/media. Assistant bubbles run on a black background withredGlowPulse; user bubbles adoptvar(--secondary-color)withblueGlowPulse.
Scroll & layout behavior
.messagesContaineris a flex child (flex: 1) sandwiched between the header and form. The plumbing fortextareaExpanded/messagesContainerShrunkis already wired so we can reclaim vertical space when we eventually lock the textarea height, though todayChatFormalways reports'auto'so the transcript keeps its full height.- Scroll management keeps the modal usable while the surrounding page scrolls:
scrollbar-gutter: stableavoids layout jumps when the inner scrollbar appears,overscroll-behavior: containprevents touchpads from propagating scroll to the document, and the MutationObserver inuseEffectreapplies the pinned scroll position whenever the DOM mutates mid-stream. scrollNodeBottomIntoViewAndPinlets the parent jump directly to the last user message using bounding-rect math, so the view tracks interactive elements rather than just settingscrollTop = scrollHeight.
Streaming affordances
- A looping loading indicator (
RapBotRito is cooking up bars…) shows wheneverisLoadingis true. - Error recovery happens via the global
ErrorModal(theerrorprop passed toChatMessagesis currentlynull), so the inline error block is dormant until we decide to expose transcript-level retries again.
Message Content Segments (MessageContent.tsx)
While ChatMessages decides where to render, MessageContent decides how to render each part:
- It iterates every
partsentry, callsparseContentWithMedia(part.text), and feeds the resultingMediaSegment[]toRenderSegment. - Supported segments already include
formattedText, headings (h1–h4),<music>cues,<gif>s, inline<svg>strings, chain logos, and the<goodbye />refresh animation. When the assistant emits a<mongoose>-style inline renderer,RenderSegmentchooses the right presenter so CSS classes can color-match the active bubble. - Because inline renderer details deserve their own walkthrough (component registry, hydrated image store, etc.), we will ship a dedicated doc—see the placeholder card below.
Composer & Controls (ChatForm.tsx & .module.css)
The composer is tuned for both mobile comfort and streaming control.
Auto-sizing input
ChatFormstores atextareaRefand reads the--textarea-max-heightCSS variable viagetComputedStyle. Desktop uses240px, while the mobile media query drops it to180px—this is the “special structuring logic” that keeps the modal from covering the whole screen when you open the keyboard.resizeToContentruns on mount, on every input change, and onwindow.resize. It temporarily setsheight: auto, measures the scroll height, clamps to the CSS var, and toggles the textarea’s overflow so the user never sees double scrollbars.- On submit we currently reset straight back to
'auto', so the shrink state never kicks in, but the prop plumbing is ready for when we introduce capped multi-line states in a future iteration.
Submission controls
handleKeyDowninterceptsEnter(withoutShift) to submit only when the chat status isready/errorand the input is not disabled. HoldingShiftrespects multi-line editing.- The icon buttons share a single
.iconButtonstyle: square, bordered, and animated on hover. The send button swaps to a textual...indicator wheneverisLoadingis true. - When the AI is streaming, the send button swaps to a coral
Stoppill that callsonStop, mirroring theuseChatcancel method. - The trash button sits on the left and calls
onTrash, which in turn opensConfirmResetModal—this keeps destructive actions gated by the modal store.
State-aware logging
The component’s createLogger helper reads publicConfig.logLevel and prefixes every log line with timestamps and scope labels. It’s invaluable when debugging how mobile Safari handles keyboard resize events or why a submit was blocked.
Packages & Stores In Play
@ai-sdk/react—useChatprovidesmessages,status,sendMessage,stop, andregenerate. The modal reacts to those states to show the stop button, spinner, and inline error.wagmi—UserHeaderWithWagmiusesuseAccount,useEnsName, anduseChainIdto decorate user bubbles with ENS avatars, addresses, and network metadata.- Custom stores (
useChatModeStore,useModalStore,useToolActivityStore,useMusic) — Zustand stores let the UI lock modes, open/close modals, attach tool chips to assistant messages, and stream music commands without prop-drilling. MusicProviderandMusicBar— even though the audio controls live outside this page’s scope, the modal always renders them under the chat. The stack layout keeps enough breathing room for the player while still letting the entire stack scroll with the document.createToolAwareTransport+useHydrateToolImages— transport injects mode metadata for everyuseChatrequest, while the hydration hook makes sure<img src="store://..." />segments referenced byMessageContenthave bytes ready before they render.
Related Docs & Placeholders
Coming soon: a registry-level tour of RenderSegment and the custom components that turn <gif />, <music />, and <goodbye /> tags into UI.
Placeholder for a deep dive on ToolActivityRow, chip grouping, and error/success badges that sit inside assistant bubbles.
Future coverage of MusicProvider, MusicBar, and how ChatWithPlayer keeps the audio controls aligned with the modal.
How UserHeaderWithWagmi and WagmiBoundary visually represent the user’s injected context.