Chat welcome + active conversation: composer controls, layout, header

Measured welcome-screen + active-conversation feedback:

Composer redesigned with two variants (no Send button — Enter to send,
Shift+Enter for newline; attach + screenshot icon affordances on the
left). 'welcome' = short single-row 672px-capped pill, icons inline;
'active' = taller two-row card (textarea on top, 36px icon-button row
below), placeholder switches to 'Type your message…'. A cream Stop pill
appears while the agent is generating, wired to a real useChat.stop()
that aborts the SSE and finalizes the partial reply (new 'stopped'
reducer action).

Empty state un-pinned: the composer is now part of the centered welcome
stack (avatar → heading → subtitle → composer → chips) instead of being
pinned to the viewport bottom. Subtitle dropped to 12px. Hero avatar
kept at 126px but with a proportional 40px squircle radius (a flat 17px
read as square at that size).

Chat header sized up to match: 58px avatar (new Avatar 'header' size),
name 18px/600 neutral-200, role 14px. The neutral-500 role/subtitle tone
nudged to #7d7d7d to clear AA contrast on #0a0a0a.

84 unit + 31 functional E2E green; verified both states live.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-11 04:27:27 -05:00
co-authored by Claude Fable 5
parent 660dbc6583
commit 7393b6538b
9 changed files with 180 additions and 50 deletions
+41 -14
View File
@@ -34,8 +34,13 @@ export function ChatWorkspace({
sessions,
}: ChatWorkspaceProps) {
const router = useRouter();
const { state, send, decide } = useChat(agent.id, sessionKey, initialMessages);
const { state, send, decide, stop } = useChat(
agent.id,
sessionKey,
initialMessages,
);
const [draft, setDraft] = useState("");
const empty = state.messages.length === 0;
const [{ sessions: showSessions }] = useQueryStates(panelParsers, {
shallow: true,
});
@@ -73,22 +78,44 @@ export function ChatWorkspace({
<section className="flex min-w-0 flex-1 flex-col">
<ChatHeader agent={agent} onNewSession={newSession} />
<div className="mx-auto flex w-full max-w-3xl min-w-0 flex-1 flex-col overflow-hidden px-4">
{state.messages.length === 0 ? (
<WelcomeState agent={agent} onPick={setDraft} />
) : (
<MessageList
messages={state.messages}
{empty ? (
// Empty state: the composer is part of the centered welcome
// stack (avatar → heading → subtitle → composer → chips).
<WelcomeState
agent={agent}
onDecide={decide}
onPick={setDraft}
composer={
<Composer
agentName={agent.name}
variant="welcome"
disabled={state.streaming}
onSend={send}
onStop={stop}
draft={draft}
onDraftChange={setDraft}
/>
}
/>
) : (
// Active conversation: transcript scrolls, the taller two-row
// composer is pinned below it.
<>
<MessageList
messages={state.messages}
agent={agent}
onDecide={decide}
/>
<Composer
agentName={agent.name}
variant="active"
disabled={state.streaming}
onSend={send}
onStop={stop}
draft={draft}
onDraftChange={setDraft}
/>
</>
)}
<Composer
agentName={agent.name}
disabled={state.streaming}
onSend={send}
draft={draft}
onDraftChange={setDraft}
/>
</div>
</section>
<DevicePanel agent={agent} />