fix(coach): default to new chat and never dead-end on conversations

Reverse the resume-most-recent entry default: opening the Coach now lands
on the new-chat hero. The only exception is an unread coach-initiated
message (nudge-status `unread`), which auto-opens the most-recent thread so
the user sees what the Coach wrote. The `?c=<id>` deep-link is unchanged.

Add a dedicated, always-visible Conversations button (plus a New chat
button) to the page surface so reaching past conversations no longer hides
in the composer menu. The drawer's Conversations handoff now routes to
`/coach?view=conversations`, which opens the history drawer on arrival, so
the side-view to full-view path always shows the list instead of a blank
pane.
This commit is contained in:
Marc-André Bombeck committed 2026-06-21 13:59:05 +02:00
1 parent a86abd3662
commit c6f4772606
4 files changed
+120 -18

No files matched your search

@@ -135,4 +135,25 @@ describe("<CoachConversation> page deep-link (#67)", () => {
// though recent conversations exist in the cache.
expect(html).toContain('data-slot="coach-hero"');
});
// v1.19.1 (C2) — a dedicated, always-visible Conversations button on the
// page surface (not buried in the composer `+` menu).
it("renders the dedicated Conversations button on the page surface", () => {
const html = render(<CoachConversation surface="page" />, makeClient());
expect(html).toContain('data-slot="coach-page-conversations"');
expect(html).toContain('data-slot="coach-page-new-chat"');
});
// v1.19.1 (C5) — entering via the drawer handoff (`?view=conversations`)
// keeps the new-chat hero (no thread auto-resumed) while opening the
// history drawer; the toolbar + button stay present so the pane is never
// a blank dead-end.
it("keeps the hero and toolbar when opened with openHistoryOnMount", () => {
const html = render(
<CoachConversation surface="page" openHistoryOnMount />,
makeClient(),
);
expect(html).toContain('data-slot="coach-hero"');
expect(html).toContain('data-slot="coach-page-conversations"');
});
});
@@ -3,7 +3,7 @@
import { useEffect, useReducer, useState } from "react";
import Link from "next/link";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Plus, Settings, Sparkles } from "lucide-react";
import { MessagesSquare, Plus, Settings, Sparkles } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
@@ -137,6 +137,13 @@ export interface CoachConversationProps {
* "new chat" or thread switch is never overridden.
*/
autoOpenMostRecent?: boolean;
/**
* v1.19.1 (C5) — page surface only: open the conversation-history drawer
* once on mount. The drawer's "Conversations" handoff routes to
* `/coach?view=conversations`, which sets this so the user always sees the
* conversation list on arrival instead of a blank new-chat pane.
*/
openHistoryOnMount?: boolean;
}
export function CoachConversation({
@@ -152,6 +159,7 @@ export function CoachConversation({
surface,
initialConversationId,
autoOpenMostRecent = false,
openHistoryOnMount = false,
}: CoachConversationProps) {
const { t } = useTranslations();
@@ -165,7 +173,12 @@ export function CoachConversation({
// the composer's `+` actions menu. Replaces the old inline collapsible
// rail + top rail-tray strip; the top header bar is gone entirely on the
// page so the composer is the single control hub.
const [historyDrawerOpen, setHistoryDrawerOpen] = useState(false);
// v1.19.1 (C5) — seed the history drawer open when the page is entered via
// the drawer's "Conversations" handoff (`?view=conversations`), so the
// list is on screen immediately rather than a blank hero.
const [historyDrawerOpen, setHistoryDrawerOpen] = useState(
() => openHistoryOnMount,
);
const [inputValue, setInputValue] = useResettableValue(prefill ?? "");
// v1.16.4 — self-context backflow: `pendingAdopt` raises a quiet
// offer to fold a clarifying-question answer back into the
@@ -484,6 +497,37 @@ export function CoachConversation({
data-variant={surface}
className={cn("flex min-h-0 flex-1 flex-col", className)}
>
{/* v1.19.1 (C2) — a clear, always-visible "Conversations" button on
the page surface. The composer's `+` menu still carries the same
action, but the maintainer wanted an obvious, dedicated affordance
to reach past conversations rather than one buried in a menu. */}
<div
data-slot="coach-page-toolbar"
className="flex shrink-0 items-center justify-between gap-2 px-4 pt-2 sm:px-6"
>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setHistoryDrawerOpen(true)}
data-slot="coach-page-conversations"
className="text-muted-foreground hover:text-foreground -ml-1 gap-1.5"
>
<MessagesSquare className="size-4" aria-hidden="true" />
{t("insights.coach.historyTitle")}
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={handleNewChat}
data-slot="coach-page-new-chat"
className="text-muted-foreground hover:text-foreground -mr-1 gap-1.5"
>
<Plus className="size-4" aria-hidden="true" />
{t("insights.coach.newChat")}
</Button>
</div>
{heroActive ? (
<CoachHero composer={composerNode} />
) : (
@@ -88,6 +88,17 @@ export function CoachDrawer({ open, onOpenChange, prefill }: CoachDrawerProps) {
router.push("/coach");
}, [onOpenChange, router]);
// v1.19.1 (C5) — the drawer's "Conversations" affordance hands off to the
// full page WITH the `?view=conversations` flag so the page opens the
// conversation-history drawer on arrival. Previously it shared
// `handleMaximize` and, after the v1.19.1 new-chat default (C1), landed on
// a blank hero with no list — the dead-end the maintainer reported.
const handleOpenConversations = useCallback(() => {
onOpenChange(false);
resetRef.current?.();
router.push("/coach?view=conversations");
}, [onOpenChange, router]);
return (
<Sheet open={open} onOpenChange={handleOpenChange}>
<SheetContent
@@ -118,9 +129,10 @@ export function CoachDrawer({ open, onOpenChange, prefill }: CoachDrawerProps) {
autoFocusComposer
registerReset={registerReset}
// v1.16.1 — the "Conversations" affordance hands off to the
// full-page route (same path as maximize) instead of opening
// the broken in-panel left tray.
onRequestFullView={handleMaximize}
// full-page route instead of opening the broken in-panel left
// tray. v1.19.1 (C5) — it now carries `?view=conversations` so the
// page opens the history list on arrival, never a blank hero.
onRequestFullView={handleOpenConversations}
// Radix requires an accessible name + description on the
// dialog content. Mount them through the title/description
// render-props so the page can swap in a plain heading.