fix(nexus): use flex layout for assistant chat thread container

Phase 9 used `h-[calc(100vh-320px)] min-h-[320px]` on the thread
wrapper because `@tanstack/react-virtual` needs an explicit height
for its virtual scroll root. The calc was brittle — any change to
TopStrip or input bar sizing broke the math and let the thread
overflow or collapse below 320px.

Phase 16a replaces the calc with a pure flex chain:

- PersonalAssistant forks its root column: when a conversation is
  active it renders a `flex-1 min-h-0` child that centers the
  message list at max-w-[760px] and delegates scroll to the list's
  own internal virtual scroller. When no conversation is active the
  old `overflow-auto` wrapper remains so the greeting and empty
  state still scroll.
- ChatMessageList's root becomes `flex min-h-0 flex-1 flex-col`,
  and its inner scroll div becomes `flex-1 min-h-0 overflow-auto`.
  The virtualizer now measures the actual flex-derived height,
  which tracks input-bar size changes automatically.

Tests: src/components/frame/ + src/components/assistant/ +
ChatMessageList.test — 109 passed (6 todo).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nexus Dev 2026-04-11 15:58:17 +00:00
parent 14dd8dbfa5
commit 4ff707bdcb
2 changed files with 33 additions and 25 deletions

View file

@ -138,10 +138,10 @@ export function ChatMessageList({
}
return (
<div className="relative flex-1">
<div className="relative flex min-h-0 flex-1 flex-col">
<div
ref={parentRef}
className="h-full overflow-auto p-3"
className="flex-1 min-h-0 overflow-auto p-3"
onScroll={handleScroll}
>
<div

View file

@ -282,31 +282,39 @@ export function PersonalAssistant() {
</div>
)}
{/* Conversation thread — full-bleed, max-width 760px centered */}
<div className="flex-1 min-h-0 overflow-auto">
<div className="mx-auto w-full max-w-[760px] px-6 py-8">
{showHomeGreeting && (
<AssistantHomeGreeting
status={homeStatus}
userName={selectedCompany?.name ?? null}
{/* Conversation thread full-bleed, max-width 760px centered.
Phase 16a replaced the old `h-[calc(100vh-320px)]` wrapper with
a pure flex-1 column so the virtualizer inherits its scroll
height from the flex layout instead of hard-coded viewport
arithmetic. Greeting and empty-state variants still scroll via
the outer `overflow-auto`; the active-conversation variant
delegates scroll to ChatMessageList's internal virtual scroller. */}
{hasActiveConversation && selectedConvId ? (
<div className="flex flex-1 min-h-0 justify-center">
<div className="flex w-full max-w-[760px] min-h-0 flex-1 flex-col">
<ChatMessageList
conversationId={selectedConvId}
streamingContent={streamingContent}
isStreaming={isStreaming}
/>
)}
{hasActiveConversation && selectedConvId && (
<div className="h-[calc(100vh-320px)] min-h-[320px]">
<ChatMessageList
conversationId={selectedConvId}
streamingContent={streamingContent}
isStreaming={isStreaming}
/>
</div>
)}
{!showHomeGreeting && !selectedConvId && conversationCount === 0 && (
<p className="text-sm text-muted-foreground">No conversations yet.</p>
)}
</div>
</div>
</div>
) : (
<div className="flex-1 min-h-0 overflow-auto">
<div className="mx-auto w-full max-w-[760px] px-6 py-8">
{showHomeGreeting && (
<AssistantHomeGreeting
status={homeStatus}
userName={selectedCompany?.name ?? null}
/>
)}
{!showHomeGreeting && !selectedConvId && conversationCount === 0 && (
<p className="text-sm text-muted-foreground">No conversations yet.</p>
)}
</div>
</div>
)}
{/* Phase 12 promote-to-project overlay. Absolutely positioned so
the chat canvas is covered during the animation while keeping