fix(tui): big-session /resume now renders without first keystroke

useVirtualHistory set up its useSyncExternalStore subscription during
the first render, when scrollRef.current was still null (the ScrollBox
ref attaches during commit, after render). Its useCallback for
subscribe had a stable scrollRef identity as its only dep, so it never
re-subscribed once the ref actually attached — the hook stayed stuck
with vp=0, top=0, no scroll subscription. Small sessions fit entirely
in cold-start so you didn't notice; big /resume sessions got sliced to
the last 40 items with a huge topSpacer and the viewport sat on empty
space until some unrelated state change (e.g. a keystroke) re-rendered
and finally read a real vp.

- flip a hasScrollRef flag in useLayoutEffect once the ref attaches and
  add it to the subscribe useCallback deps so useSyncExternalStore
  rearms with a real subscription
- on resume, scrollToBottom() after history hydrates so the ScrollBox
  lands at the newest messages instead of scrollTop=0 (stickyScroll
  doesn't auto-engage on the initial empty→full dump)
This commit is contained in:
Brooklyn Nicholson 2026-04-17 11:04:29 -05:00
parent 8f553a55b2
commit 42721dbe1c
3 changed files with 16 additions and 3 deletions

View file

@ -272,6 +272,7 @@ export function useMainApp(gw: GatewayClient) {
gw,
panel,
rpc,
scrollRef,
setHistoryItems,
setLastUserMsg,
setSessionStartedAt,