fix(coach): keep the last message clear of the New conversation button

Where the composer fills the column (a phone, the drawer, a narrow
column), the New conversation button floats above the composer and
covered the last message's time and actions at 390 px. The thread now
keeps the button's height free below the last message while the button
floats, the way the shell clears the Coach button, and drops back to its
plain gutter once the button sits beside a wide composer.
This commit is contained in:
Marc-André Bombeck committed 2026-11-12 12:58:58 +01:00
1 parent 367fcd5332
commit 4105e2c85f
4 files changed
+25 -16

No files matched your search

@@ -1,7 +1,7 @@
{
"$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.",
"count": 2876,
"total": 9164,
"total": 9161,
"entries": {
"__tests__/account-payload-consumer-guard.test.ts": 2,
"__tests__/acting-account-boundary-guard.test.ts": 7,
@@ -925,7 +925,7 @@
"components/insights/coach-panel/guided-questions-card.tsx": 1,
"components/insights/coach-panel/guided-questions-machine.ts": 2,
"components/insights/coach-panel/memory-note.tsx": 1,
"components/insights/coach-panel/message-thread.tsx": 31,
"components/insights/coach-panel/message-thread.tsx": 28,
"components/insights/coach-panel/method-line.tsx": 1,
"components/insights/coach-panel/mobile-rail-tray.tsx": 2,
"components/insights/coach-panel/read-aloud.tsx": 8,
@@ -252,6 +252,16 @@ describe("<MessageThread>", () => {
);
});
it("keeps the floating New conversation button's height free below the last message", () => {
const html = render(<MessageThread conversation={baseConversation} />);
const scroller =
html.match(/<div[^>]*data-slot="coach-message-thread"[^>]*>/)?.[0] ?? "";
expect(scroller).toContain("pb-20");
// Back to the plain gutter once the button sits beside the composer.
expect(scroller).toContain("@min-[57rem]/coach-thread:pb-6");
});
it("folds an older message's provenance into the thought-process row, never a raw key", () => {
const html = render(<MessageThread conversation={baseConversation} />);
expect(html).toContain('data-slot="coach-turn-steps-done"');
@@ -640,27 +640,25 @@ export function MessageThread({
one scroll container. */}
<div
data-slot="coach-message-thread-frame"
className="relative flex min-h-0 flex-1 flex-col"
className="@container/coach-thread relative flex min-h-0 flex-1 flex-col"
>
<div
ref={scrollerRef}
data-slot="coach-message-thread"
className={cn(
// v1.18.6.1 — `min-h-0 flex-1` (not `h-full`) so the scroll region
// resolves its height from the flex parent rather than a 100%-of-auto
// chain that let the thread grow instead of scroll.
// v1.18.7 — calmer vertical rhythm (gap-6) and more generous top/
// bottom breathing room so the conversation reads like a document,
// not a chat log packed against the chrome.
// `min-h-0 flex-1`, not `h-full`: the region takes its height from
// the flex parent, so the thread scrolls instead of growing.
"flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto px-4 py-6 sm:px-6",
// v1.18.6 v1.18.7 — on the wide page surface an edge-to-
// edge thread sprawled the prose past a readable measure. Centre the
// content on a narrower, calmer Claude/ChatGPT-like column
// (`max-w-2xl`) via an `mx-auto` inner gutter; the scrollbar still
// rides the surface edge. The drawer surface is already narrow, so
// the cap only bites on the wide page surface.
// A readable measure on the wide page; the scrollbar stays on the
// surface edge.
"[&>*]:mx-auto [&>*]:w-full [&>*]:max-w-2xl",
"scroll-smooth",
// Every surface with a thread floats New conversation over its
// bottom edge while the composer fills the column (`NewChatFab`),
// so the last message keeps the button's height free, as the
// shell clears the Coach button. The button moves beside a 54rem
// composer, which the column is at 57rem with its padding.
"pb-20 @min-[57rem]/coach-thread:pb-6",
)}
>
{[
@@ -16,7 +16,8 @@ import { cn } from "@/lib/utils";
* column is wide enough, it takes the bottom right corner beside the
* composer, where the Coach button sits on other pages; where the composer
* fills the width (a phone, the drawer, a narrow column), it rises just
* above the composer's top edge so it never covers the field.
* above the composer's top edge so it never covers the field, and the thread
* keeps its height free below the last message.
*
* The parent is the composer's wrapper and must be `relative` and an
* `@container/composer`.