mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
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:
1 parent
c07ffe78c0
commit
dc75ae69ae
14 files changed
+414
-255
No files matched your search
+46
-22
@@ -123,25 +123,29 @@ test.describe("document vault — chat about a document", () => {
|
||||
);
|
||||
|
||||
await page.goto(`/documents?doc=${CONTENT_DOC_ID}`);
|
||||
const sheet = page.getByRole("dialog");
|
||||
const sheet = page.locator('[data-slot="responsive-sheet-content"]');
|
||||
await expect(sheet).toBeVisible();
|
||||
|
||||
// The neutral Coach icon lives in the detail-sheet header; tapping it
|
||||
// opens the doc-scoped chat drawer (a sibling overlay).
|
||||
const open = sheet.locator('[data-slot="document-chat-open"]');
|
||||
await expect(open).toBeVisible();
|
||||
await open.click();
|
||||
|
||||
const drawer = page.locator('[data-slot="document-chat-drawer"]');
|
||||
await expect(drawer).toBeVisible();
|
||||
await expect(
|
||||
sheet.locator('[data-slot="document-chat-log"]'),
|
||||
drawer.locator('[data-slot="document-chat-log"]'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
sheet.locator('[data-slot="document-chat-empty"]'),
|
||||
drawer.locator('[data-slot="document-chat-empty"]'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
sheet.locator('[data-slot="document-chat-input"]'),
|
||||
drawer.locator('[data-slot="document-chat-input"]'),
|
||||
).toBeVisible();
|
||||
// The always-on safety note is present.
|
||||
await expect(
|
||||
sheet.locator('[data-slot="document-chat-safety"]'),
|
||||
drawer.locator('[data-slot="document-chat-safety"]'),
|
||||
).toContainText("not medical advice");
|
||||
});
|
||||
|
||||
@@ -238,16 +242,18 @@ test.describe("document vault — chat about a document", () => {
|
||||
);
|
||||
|
||||
await page.goto(`/documents?doc=${CONTENT_DOC_ID}`);
|
||||
const sheet = page.getByRole("dialog");
|
||||
const sheet = page.locator('[data-slot="responsive-sheet-content"]');
|
||||
await expect(sheet).toBeVisible();
|
||||
|
||||
await sheet.locator('[data-slot="document-chat-open"]').click();
|
||||
const input = sheet.locator('[data-slot="document-chat-input"]');
|
||||
const drawer = page.locator('[data-slot="document-chat-drawer"]');
|
||||
await expect(drawer).toBeVisible();
|
||||
const input = drawer.locator('[data-slot="document-chat-input"]');
|
||||
await input.fill(QUESTION);
|
||||
await sheet.locator('[data-slot="document-chat-send"]').click();
|
||||
await drawer.locator('[data-slot="document-chat-send"]').click();
|
||||
|
||||
// The user's question and the streamed/settled reply both land in the log.
|
||||
const log = sheet.locator('[data-slot="document-chat-log"]');
|
||||
const log = drawer.locator('[data-slot="document-chat-log"]');
|
||||
await expect(log.getByText(QUESTION)).toBeVisible();
|
||||
await expect(log.getByText(/findings are unremarkable/)).toBeVisible();
|
||||
// The reply settled to a single assistant bubble (no duplicate).
|
||||
@@ -354,15 +360,17 @@ test.describe("document vault — chat about a document", () => {
|
||||
);
|
||||
|
||||
await page.goto(`/documents?doc=${CONTENT_DOC_ID}`);
|
||||
const sheet = page.getByRole("dialog");
|
||||
const sheet = page.locator('[data-slot="responsive-sheet-content"]');
|
||||
await expect(sheet).toBeVisible();
|
||||
|
||||
await sheet.locator('[data-slot="document-chat-open"]').click();
|
||||
const input = sheet.locator('[data-slot="document-chat-input"]');
|
||||
const drawer = page.locator('[data-slot="document-chat-drawer"]');
|
||||
await expect(drawer).toBeVisible();
|
||||
const input = drawer.locator('[data-slot="document-chat-input"]');
|
||||
await input.fill(QUESTION);
|
||||
await sheet.locator('[data-slot="document-chat-send"]').click();
|
||||
await drawer.locator('[data-slot="document-chat-send"]').click();
|
||||
|
||||
const log = sheet.locator('[data-slot="document-chat-log"]');
|
||||
const log = drawer.locator('[data-slot="document-chat-log"]');
|
||||
// The describe-only answer lands; the panel renders the model's <script>
|
||||
// payload as literal text (proof it is a React text child, not HTML).
|
||||
await expect(log.getByText(/treat it as document content/)).toBeVisible();
|
||||
@@ -387,18 +395,27 @@ test.describe("document vault — chat about a document", () => {
|
||||
// AI_PROBE_DOC_ID carries no content index (hasContentIndex === false).
|
||||
await mockAiEnabled(page);
|
||||
await page.goto(`/documents?doc=${AI_PROBE_DOC_ID}`);
|
||||
const sheet = page.getByRole("dialog");
|
||||
const sheet = page.locator('[data-slot="responsive-sheet-content"]');
|
||||
await expect(sheet).toBeVisible();
|
||||
|
||||
// A provider is available, so the Coach entry still shows — it opens the
|
||||
// drawer, whose body carries the calm read-it-first hint (never an error)
|
||||
// and no composer, because there is nothing indexed to ground on yet.
|
||||
const open = sheet.locator('[data-slot="document-chat-open"]');
|
||||
await expect(open).toBeVisible();
|
||||
await open.click();
|
||||
|
||||
const drawer = page.locator('[data-slot="document-chat-drawer"]');
|
||||
await expect(drawer).toBeVisible();
|
||||
await expect(
|
||||
sheet.locator('[data-slot="document-chat-not-indexed"]'),
|
||||
drawer.locator('[data-slot="document-chat-not-indexed"]'),
|
||||
).toBeVisible();
|
||||
// No chat entry, and no error styling.
|
||||
await expect(sheet.locator('[data-slot="document-chat-open"]')).toHaveCount(
|
||||
0,
|
||||
);
|
||||
// No composer, and no error styling.
|
||||
await expect(
|
||||
sheet.locator('[data-slot="document-chat"] [role="alert"]'),
|
||||
drawer.locator('[data-slot="document-chat-input"]'),
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
drawer.locator('[data-slot="document-chat"] [role="alert"]'),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
@@ -410,7 +427,7 @@ test.describe("document vault — chat about a document", () => {
|
||||
// No mocks — the seeded demo user has no AI provider configured, so the AI
|
||||
// area collapses to the calm pointer and the chat slot never renders.
|
||||
await page.goto(`/documents?doc=${CONTENT_DOC_ID}`);
|
||||
const sheet = page.getByRole("dialog");
|
||||
const sheet = page.locator('[data-slot="responsive-sheet-content"]');
|
||||
await expect(sheet).toBeVisible();
|
||||
|
||||
await expect(
|
||||
@@ -419,6 +436,13 @@ test.describe("document vault — chat about a document", () => {
|
||||
await expect(
|
||||
sheet.getByRole("link", { name: "Open AI settings" }),
|
||||
).toBeVisible();
|
||||
await expect(sheet.locator('[data-slot="document-chat"]')).toHaveCount(0);
|
||||
// No provider → no Coach entry in the header and no chat drawer mounted.
|
||||
await expect(sheet.locator('[data-slot="document-chat-open"]')).toHaveCount(
|
||||
0,
|
||||
);
|
||||
await expect(
|
||||
page.locator('[data-slot="document-chat-drawer"]'),
|
||||
).toHaveCount(0);
|
||||
await expect(page.locator('[data-slot="document-chat"]')).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user