From e5de72cae221d8ddc414b147a876648400cab622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 9 Jul 2026 17:27:47 +0200 Subject: [PATCH] feat(documents): hide manual AI actions under auto-read, trim suggestion chrome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When "read documents automatically with AI" is on, the detail sheet no longer shows the manual per-document action row (Read / Suggest / Summarise) — reading already happens on upload, so the buttons only nagged. The section header and the searchable status pill stay, and content search keeps working. With the setting off the manual controls render exactly as before. Drop the "review before saving" heading and its grey sub-caption from the suggestion panel; the reviewable fields and the apply / dismiss controls stay. Remove the now-unused documents.assist.reviewTitle / reviewHint keys from every locale. --- e2e/documents-ai.spec.ts | 10 +- e2e/documents-auto-read.spec.ts | 42 ++++++- e2e/documents-chat.spec.ts | 5 + messages/de.json | 2 - messages/en.json | 2 - messages/es.json | 2 - messages/fr.json | 2 - messages/it.json | 2 - messages/pl.json | 2 - .../__tests__/document-ai-panels.test.tsx | 12 +- .../__tests__/document-ai-section.test.tsx | 24 +++- .../documents/document-ai-panels.tsx | 16 +-- .../documents/document-ai-section.tsx | 106 ++++++++++-------- .../documents/document-detail-sheet.tsx | 6 + .../documents/use-document-assist.ts | 21 ++++ 15 files changed, 168 insertions(+), 86 deletions(-) diff --git a/e2e/documents-ai.spec.ts b/e2e/documents-ai.spec.ts index 44170a542..096aaa98e 100644 --- a/e2e/documents-ai.spec.ts +++ b/e2e/documents-ai.spec.ts @@ -110,6 +110,13 @@ async function mockAiEnabled( error: null, }), ); + // The detail sheet reads the auto-AI-read opt-in to decide whether to show + // the manual per-document action row. Default it OFF here so the manual + // Read / Suggest / Summarise actions render for these assertions; an unmocked + // fetch would leave the sheet reading a live value. + await page.route("**/api/auth/me/documents-auto-ai-read", (route) => + fulfilJson(route, { data: { documentsAutoAiRead: false }, error: null }), + ); } test.describe("document vault — AI assist + content search", () => { @@ -172,9 +179,6 @@ test.describe("document vault — AI assist + content search", () => { const review = sheet.locator('[data-slot="assist-suggestion-review"]'); await expect(review).toBeVisible(); - await expect( - review.getByText("AI suggestion — review before saving"), - ).toBeVisible(); // Applying the title seeds the EDITABLE field only — no write yet. await review.getByRole("button", { name: "Use title" }).click(); diff --git a/e2e/documents-auto-read.spec.ts b/e2e/documents-auto-read.spec.ts index db4da6e05..702258ada 100644 --- a/e2e/documents-auto-read.spec.ts +++ b/e2e/documents-auto-read.spec.ts @@ -44,9 +44,21 @@ async function fulfilJson( */ async function mockDocumentCapability( page: Page, - opts: { egress: "local" | "external"; pdfSupported?: boolean }, + opts: { + egress: "local" | "external"; + pdfSupported?: boolean; + /** The per-user auto-AI-read opt-in. OFF keeps the manual action row. */ + autoRead?: boolean; + }, ): Promise { const pdfSupported = opts.pdfSupported ?? true; + const autoRead = opts.autoRead ?? false; + await page.route("**/api/auth/me/documents-auto-ai-read", (route) => + fulfilJson(route, { + data: { documentsAutoAiRead: autoRead }, + error: null, + }), + ); await page.route("**/api/documents/inbound/usage", (route) => fulfilJson(route, { data: { @@ -184,8 +196,9 @@ test.describe("automatic AI reading — vault per-document contract", () => { const sheet = page.getByRole("dialog"); await expect(sheet).toBeVisible(); - // The per-document read is the explicit path — the action is present and the - // user must tap it (auto-read never removes the affordance, only the tap). + // With auto-read OFF the per-document read is the explicit path — the action + // is present and the user taps it. (When auto-read is ON the row collapses; + // that is covered by its own test below.) await expect(sheet.locator('[data-slot="document-read-ai"]')).toBeVisible(); }); @@ -234,4 +247,27 @@ test.describe("automatic AI reading — vault per-document contract", () => { await expect(sheet.locator('[data-slot="document-read-ai"]')).toBeVisible(); }); + + test("with auto-read ON the sheet drops the manual AI action row", async ({ + page, + }) => { + // Auto-read reads every upload with no per-document tap, so the manual + // Read / Suggest actions are redundant and collapse away. The section + // header and the searchable status pill stay. + await mockDocumentCapability(page, { egress: "external", autoRead: true }); + + await page.goto(`/documents?doc=${AI_PROBE_DOC_ID}`); + const sheet = page.getByRole("dialog"); + await expect(sheet).toBeVisible(); + + const section = sheet.locator('[data-slot="document-ai-section"]'); + await expect(section).toBeVisible(); + await expect( + section.locator('[data-slot="content-search-status"]'), + ).toBeVisible(); + await expect(sheet.locator('[data-slot="document-read-ai"]')).toHaveCount( + 0, + ); + await expect(sheet.locator('[data-slot="assist-suggest"]')).toHaveCount(0); + }); }); diff --git a/e2e/documents-chat.spec.ts b/e2e/documents-chat.spec.ts index 9d693a5ae..75987d1a6 100644 --- a/e2e/documents-chat.spec.ts +++ b/e2e/documents-chat.spec.ts @@ -83,6 +83,11 @@ async function mockAiEnabled( error: null, }), ); + // The detail sheet reads the auto-AI-read opt-in; default it OFF so the AI + // section renders its full set of controls alongside the chat entry. + await page.route("**/api/auth/me/documents-auto-ai-read", (route) => + fulfilJson(route, { data: { documentsAutoAiRead: false }, error: null }), + ); } test.describe("document vault — chat about a document", () => { diff --git a/messages/de.json b/messages/de.json index 9702f9964..cc0acbaaf 100644 --- a/messages/de.json +++ b/messages/de.json @@ -8229,8 +8229,6 @@ "assist": { "suggest": "Details vorschlagen", "suggesting": "Wird gelesen…", - "reviewTitle": "KI-Vorschlag — vor dem Speichern prüfen", - "reviewHint": "Es wird nichts gespeichert, bis du es übernimmst. Vorschläge können falsch sein — prüfe sie.", "suggestedTitle": "Vorgeschlagener Titel", "suggestedKind": "Vorgeschlagener Typ", "suggestedDate": "Vorgeschlagenes Datum", diff --git a/messages/en.json b/messages/en.json index 4744fc5c8..7f1d98165 100644 --- a/messages/en.json +++ b/messages/en.json @@ -8229,8 +8229,6 @@ "assist": { "suggest": "Suggest details", "suggesting": "Reading…", - "reviewTitle": "AI suggestion — review before saving", - "reviewHint": "Nothing is saved until you apply it. Suggestions can be wrong — check them.", "suggestedTitle": "Suggested title", "suggestedKind": "Suggested type", "suggestedDate": "Suggested date", diff --git a/messages/es.json b/messages/es.json index e2056fac8..b57d835bb 100644 --- a/messages/es.json +++ b/messages/es.json @@ -8229,8 +8229,6 @@ "assist": { "suggest": "Sugerir detalles", "suggesting": "Leyendo…", - "reviewTitle": "Sugerencia de IA: revísala antes de guardar", - "reviewHint": "No se guarda nada hasta que la apliques. Las sugerencias pueden fallar: compruébalas.", "suggestedTitle": "Título sugerido", "suggestedKind": "Tipo sugerido", "suggestedDate": "Fecha sugerida", diff --git a/messages/fr.json b/messages/fr.json index 6501d839a..019f2e55b 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -8229,8 +8229,6 @@ "assist": { "suggest": "Suggérer des détails", "suggesting": "Lecture…", - "reviewTitle": "Suggestion IA — à vérifier avant d'enregistrer", - "reviewHint": "Rien n'est enregistré tant que vous ne l'appliquez pas. Les suggestions peuvent être erronées — vérifiez-les.", "suggestedTitle": "Titre suggéré", "suggestedKind": "Type suggéré", "suggestedDate": "Date suggérée", diff --git a/messages/it.json b/messages/it.json index 821af3453..63168d001 100644 --- a/messages/it.json +++ b/messages/it.json @@ -8229,8 +8229,6 @@ "assist": { "suggest": "Suggerisci dettagli", "suggesting": "Lettura…", - "reviewTitle": "Suggerimento IA — verifica prima di salvare", - "reviewHint": "Nulla viene salvato finché non lo applichi. I suggerimenti possono essere errati: controllali.", "suggestedTitle": "Titolo suggerito", "suggestedKind": "Tipo suggerito", "suggestedDate": "Data suggerita", diff --git a/messages/pl.json b/messages/pl.json index ebdeee94a..72e409730 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -8229,8 +8229,6 @@ "assist": { "suggest": "Zaproponuj szczegóły", "suggesting": "Odczytywanie…", - "reviewTitle": "Sugestia AI — sprawdź przed zapisaniem", - "reviewHint": "Nic nie zostanie zapisane, dopóki tego nie zastosujesz. Sugestie mogą być błędne — sprawdź je.", "suggestedTitle": "Sugerowany tytuł", "suggestedKind": "Sugerowany typ", "suggestedDate": "Sugerowana data", diff --git a/src/components/documents/__tests__/document-ai-panels.test.tsx b/src/components/documents/__tests__/document-ai-panels.test.tsx index 6e6874adf..fcdd6757e 100644 --- a/src/components/documents/__tests__/document-ai-panels.test.tsx +++ b/src/components/documents/__tests__/document-ai-panels.test.tsx @@ -14,8 +14,8 @@ import { * The AI panels' render contract: * - the unavailable state is a CALM pointer to the AI settings, never an * error, and adapts its copy to the actionable reason; - * - the suggestion card carries the review-first affordance ("review before - * saving") and applies nothing on its own — every value has its own tap; + * - the suggestion card is review-first: it applies nothing on its own — + * every value has its own explicit apply tap, and a dismiss control; * - the summary panel ALWAYS carries the "not saved · not a diagnosis" note, * even while loading, so the session-only contract is never off-screen. */ @@ -55,7 +55,7 @@ describe("", () => { }); describe("", () => { - it("presents drafts behind an explicit review-before-saving affordance", () => { + it("presents drafts as reviewable fields with explicit apply controls", () => { const html = render( ", () => { />, ); expect(html).toContain('data-slot="assist-suggestion-review"'); - expect(html).toContain("AI suggestion — review before saving"); - expect(html).toContain("Nothing is saved until you apply it"); + // The heading + grey sub-caption were retired — the panel leads straight + // with the reviewable draft fields. + expect(html).not.toContain("review before saving"); + expect(html).not.toContain("Nothing is saved until you apply it"); // The suggested values are shown as reviewable drafts. expect(html).toContain("Blood panel — March 2026"); expect(html).toContain("Lab result"); diff --git a/src/components/documents/__tests__/document-ai-section.test.tsx b/src/components/documents/__tests__/document-ai-section.test.tsx index 78e963ba9..aedba8dc2 100644 --- a/src/components/documents/__tests__/document-ai-section.test.tsx +++ b/src/components/documents/__tests__/document-ai-section.test.tsx @@ -127,7 +127,6 @@ describe("", () => { ); expect(html).toContain('data-slot="assist-suggestion-review"'); expect(html).toContain("MRI knee report"); - expect(html).toContain("review before saving"); }); it("renders the transient summary panel", () => { @@ -157,6 +156,29 @@ describe("", () => { expect(html).not.toContain('data-slot="document-read-ai"'); }); + it("hides the manual AI action row when auto-read is on, keeping status + chat", () => { + const chat =
chat here
; + const html = render( + base({ aiEnabled: true, autoReadEnabled: true, chatSlot: chat }), + ); + // Auto-read reads on upload — the manual per-document actions are gone. + expect(html).not.toContain('data-slot="document-read-ai"'); + expect(html).not.toContain('data-slot="assist-suggest"'); + expect(html).not.toContain("Summarise"); + expect(html).not.toContain("Show extracted text"); + // The header, the searchable status pill, and content-search chat stay. + expect(html).toContain('data-slot="document-ai-section"'); + expect(html).toContain('data-slot="content-search-status"'); + expect(html).toContain('data-slot="chat-probe"'); + }); + + it("shows the manual AI action row when auto-read is off", () => { + const html = render(base({ aiEnabled: true, autoReadEnabled: false })); + expect(html).toContain('data-slot="document-read-ai"'); + expect(html).toContain('data-slot="assist-suggest"'); + expect(html).toContain("Summarise"); + }); + it("renders the chat slot inside the AI area only when a provider is available", () => { const chat =
chat here
; const enabled = render(base({ aiEnabled: true, chatSlot: chat })); diff --git a/src/components/documents/document-ai-panels.tsx b/src/components/documents/document-ai-panels.tsx index a0080ffac..28e764770 100644 --- a/src/components/documents/document-ai-panels.tsx +++ b/src/components/documents/document-ai-panels.tsx @@ -148,21 +148,7 @@ export function AssistSuggestionReview({ data-slot="assist-suggestion-review" className="border-primary/30 bg-primary/5 space-y-3 rounded-lg border p-3" > -
-
- -
-

- {t("documents.assist.reviewTitle")} -

-

- {t("documents.assist.reviewHint")} -

-
-
+
+ ) : null} - ) : null} - - - -
+ + +
+ ) : null} {suggestErrorKey ? (

diff --git a/src/components/documents/document-detail-sheet.tsx b/src/components/documents/document-detail-sheet.tsx index 0bd2c131b..559954de2 100644 --- a/src/components/documents/document-detail-sheet.tsx +++ b/src/components/documents/document-detail-sheet.tsx @@ -67,6 +67,7 @@ import { useIndexDocument } from "./use-content-index"; import { documentAiErrorKey, useDocumentAiCapability, + useDocumentsAutoAiRead, useDocumentSummary, useSuggestDetails, } from "./use-document-assist"; @@ -221,6 +222,9 @@ export function DocumentDetailSheet({ // The affordance itself is gated on `assistAvailable` (from usage); the // probe only resolves the transport mode + the actionable unavailable reason. const capability = useDocumentAiCapability(open && documentId !== null); + // When auto-read is ON, reading happens automatically on upload — the manual + // per-document AI action row is redundant and collapses away. + const autoRead = useDocumentsAutoAiRead(open && documentId !== null); const suggest = useSuggestDetails(); const summary = useDocumentSummary(); const indexDoc = useIndexDocument(); @@ -335,6 +339,7 @@ export function DocumentDetailSheet({ const aiEnabled = assistAvailable ?? capability.data?.available ?? false; const indexEnabled = contentIndexEnabled ?? capability.data?.available ?? false; + const autoReadEnabled = autoRead.data?.documentsAutoAiRead ?? false; const aiMode = capability.data?.mode === "text" ? "text" : "vision"; const aiTarget: DocumentAiTarget | null = doc ? { @@ -504,6 +509,7 @@ export function DocumentDetailSheet({ ({ + queryKey: queryKeys.documentsAutoAiRead(), + queryFn: () => + apiGet("/api/auth/me/documents-auto-ai-read"), + enabled, + staleTime: 60_000, + }); +} + /** * Suggest filing metadata for a stored document. Returns DRAFTS only — the * detail sheet prefills its edit fields and the user saves; this call writes