import { atom, type WritableAtom } from 'nanostores' // "Is the thread parked at the bottom" is owned by use-stick-to-bottom inside // ThreadMessageList (the scroll container). That state lives only in that // subtree, so ThreadMessageList mirrors it into these atoms for the composer, // status stack, and floating jump button — all of which render OUTSIDE the thread. // // `$threadScrolledUp` dims the composer / status stack; `$threadJumpButtonVisible` // shows the floating jump control. Both track `!isAtBottom` today, but stay // separate so their thresholds can diverge again without touching consumers. export const $threadScrolledUp = atom(false) export const $threadJumpButtonVisible = atom(false) // Skip no-op writes so subscribers don't churn on every scroll tick. const setter = (target: WritableAtom) => (value: boolean) => { if (target.get() !== value) { target.set(value) } } const setScrolledUp = setter($threadScrolledUp) const setJumpButtonVisible = setter($threadJumpButtonVisible) export const setThreadAtBottom = (isAtBottom: boolean) => { setScrolledUp(!isAtBottom) setJumpButtonVisible(!isAtBottom) } export const resetThreadScroll = () => setThreadAtBottom(true) // Cross-component bridge: the jump button lives by the composer, the viewport's // `scrollToBottom` lives inside the thread. The bridge registers a handler; the // button fires it. Mirrors the composer focus/insert emitter pattern. const handlers = new Set<() => void>() export const onScrollToBottomRequest = (handler: () => void) => { handlers.add(handler) return () => void handlers.delete(handler) } export const requestScrollToBottom = () => handlers.forEach(handler => handler()) // Inline edit grows a sticky human bubble. Fire on pointerdown so the viewport // escapes stick-to-bottom before focus/layout; close clears the edit flag when // the inline composer unmounts. const editOpenHandlers = new Set<() => void>() const editCloseHandlers = new Set<() => void>() export const onThreadEditOpen = (handler: () => void) => { editOpenHandlers.add(handler) return () => void editOpenHandlers.delete(handler) } export const notifyThreadEditOpen = () => editOpenHandlers.forEach(handler => handler()) export const onThreadEditClose = (handler: () => void) => { editCloseHandlers.add(handler) return () => void editCloseHandlers.delete(handler) } export const notifyThreadEditClose = () => editCloseHandlers.forEach(handler => handler())