Accessibility
ChatScroll from @prepared911/ui/chat-scroll.
The scroll root is role="log" with aria-live="polite" and aria-relevant="additions", named by label. A message appended to the list is announced once, in order. additions rather than the default additions text keeps a node and its text from matching twice, which some screen readers speak twice. Edits to the text of an existing message are not announced.
Children are in chronological DOM order, so reading order and focus order match what is on screen. Scrollable's reverseOrder seats a short thread at the bottom and puts the loading row at the top without reordering the DOM.
Adding older messages inserts nodes into a live region, and a polite log would read the whole page as new traffic. While loadingRow or errorRow renders, and for two animation frames after it goes away, the log is aria-live="off". Assistive technology reads a tree change after the frame that painted it, so the second frame is the grace that keeps the prepend quiet. Remove the row in the same update that adds the messages.
When the log overflows and no message holds a focusable descendant, Scrollable makes it a focusable region named by label, so the arrow keys, Page Up and Down, Home and End scroll it. The jump control is an ordinary Button after the log in tab order, and it is outside the live region, so it appearing is not announced. The exiting control is inert.
Activating the control with focus on it moves focus to the log (or, when the log is not focusable, to the list, a tabindex="-1" target) so focus never falls to the page. The list draws the shared focus ring, inset, after a keyboard activation; it appears instantly, with no transition.
When unreadCount is above zero, jumpUnreadLabel(count) names the control (for example "Jump to latest 3"). Otherwise jumpToLatestLabel names it.
Button keeps its system-color edge, and Scrollable keeps the system scrollbar.CONTRACT.md.
The overlay slot is a sibling of the log, so it is not announced as traffic. The jump control stays above it and out of its way, and the fade is a decorative background. Under forced colors the fade is a system canvas fill.