feat(documents): move document chat into the Coach drawer, neutral header entry

Replace the collapsed bottom-of-card "Chat about this document" outline
button with a neutral, ghost Coach icon pinned to the top-right of the
document detail-sheet header, mirroring the Insights ask-coach affordance.
The icon uses the muted-foreground token only — never the primary/FAB
gradient — and opens a document-scoped chat drawer.

Present the chat in the Coach chat drawer chrome (right-side sheet on
desktop, bottom sheet on phones; same width caps; close-only, no maximize
or conversations rail) with a quiet "Chatting about: <document>" scope
badge. The drawer reuses the existing pure conversation body and the
existing client hooks, so it still posts /api/documents/inbound/[id]/chat
verbatim: no tools, no health snapshot, fenced document, inbound/replay
refusal, outbound screen, numeric grounding, consent + budget + per-user
rate bucket, indexed-only, plain-text render — the route is untouched.
Thread isolation is unchanged (the general Coach filters documentId: null;
the document route always scopes { documentId: id }).

Add a small reusable headerAction slot to ResponsiveSheet for the header
icon. Retire the DocumentChatPanel container and the AI-section chatSlot;
keep the pure DocumentChatConversation body and documentChatErrorKey.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPyLteNeTWwEC154X7vLWc
This commit is contained in:
Marc-André BombeckandClaude Opus 4.8 committed 2026-07-09 18:00:06 +02:00
1 parent c07ffe78c0
commit dc75ae69ae
14 files changed
+414 -255

No files matched your search

+62 -14
View File
@@ -34,6 +34,15 @@ export interface ResponsiveSheetProps {
title: React.ReactNode;
/** Optional short description rendered beneath the title. */
description?: React.ReactNode;
/**
* Optional action rendered `shrink-0` at the trailing edge of the visible
* header row (§11 inline-action-row: title `min-w-0 flex-1`, action
* `shrink-0`). Use for a single quiet header control — e.g. the neutral
* Coach icon on the document detail sheet. Ignored when `hideHeader` is set
* (there is no visible header to hang it on). Sits left of the primitive's
* absolute close button, whose space the header already reserves.
*/
headerAction?: React.ReactNode;
/**
* Hide the visual header (title + description). The title remains
* mounted in an `sr-only` block so the accessible name is still
@@ -119,6 +128,7 @@ export function ResponsiveSheet({
onOpenChange,
title,
description,
headerAction,
hideHeader = false,
footer,
className,
@@ -156,16 +166,37 @@ export function ResponsiveSheet({
) : (
<SheetHeader
data-slot="responsive-sheet-header"
className="border-border/70 gap-1.5 border-b p-4 pr-12"
className={cn(
"border-border/70 gap-1.5 border-b p-4 pr-12",
headerAction && "flex-row items-start justify-between gap-3",
)}
>
<SheetTitle className="text-base font-semibold">
{title}
</SheetTitle>
{description ? (
<SheetDescription className="text-muted-foreground text-xs">
{description}
</SheetDescription>
) : null}
{headerAction ? (
<>
<div className="flex min-w-0 flex-1 flex-col gap-1.5">
<SheetTitle className="text-base font-semibold">
{title}
</SheetTitle>
{description ? (
<SheetDescription className="text-muted-foreground text-xs">
{description}
</SheetDescription>
) : null}
</div>
<div className="shrink-0">{headerAction}</div>
</>
) : (
<>
<SheetTitle className="text-base font-semibold">
{title}
</SheetTitle>
{description ? (
<SheetDescription className="text-muted-foreground text-xs">
{description}
</SheetDescription>
) : null}
</>
)}
</SheetHeader>
)}
<div
@@ -222,12 +253,29 @@ export function ResponsiveSheet({
) : (
<DialogHeader
data-slot="responsive-sheet-header"
className="shrink-0"
className={cn(
"shrink-0",
headerAction && "flex-row items-start justify-between gap-3 pr-9",
)}
>
<DialogTitle>{title}</DialogTitle>
{description ? (
<DialogDescription>{description}</DialogDescription>
) : null}
{headerAction ? (
<>
<div className="flex min-w-0 flex-1 flex-col gap-2">
<DialogTitle>{title}</DialogTitle>
{description ? (
<DialogDescription>{description}</DialogDescription>
) : null}
</div>
<div className="shrink-0">{headerAction}</div>
</>
) : (
<>
<DialogTitle>{title}</DialogTitle>
{description ? (
<DialogDescription>{description}</DialogDescription>
) : null}
</>
)}
</DialogHeader>
)}
<div