Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -709,33 +709,33 @@ export function DifferentialPresentationWorkflowPage({
<span className="inline-flex min-h-10 items-center rounded-lg px-3 text-sm font-bold text-[color:var(--text-muted)]">
{workflow.selectedCount} selected
</span>
<div
className="inline-flex rounded-lg border border-[color:var(--border-lux)] bg-[color:var(--surface)] p-1 shadow-[var(--shadow-inset)]"
role="group"
aria-label="Comparison density"
>
<button
type="button"
disabled
aria-disabled="true"
aria-describedby="presentation-density-unavailable"
title="Density controls — coming soon"
className="min-h-9 cursor-not-allowed rounded-md px-3 text-xs font-bold text-[color:var(--text-muted)] opacity-60"
<div className="grid gap-1">
<div
className="inline-flex rounded-lg border border-[color:var(--border-lux)] bg-[color:var(--surface)] p-1 shadow-[var(--shadow-inset)]"
role="group"
aria-label="Comparison density"
aria-describedby="density-controls-unavailable"
>
Compact
</button>
<button
type="button"
disabled
aria-disabled="true"
aria-describedby="presentation-density-unavailable"
title="Density controls — coming soon"
className="min-h-9 cursor-not-allowed rounded-md px-3 text-xs font-bold text-[color:var(--text-muted)] opacity-60"
<button
type="button"
disabled
className="min-h-9 rounded-md border border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] px-3 text-xs font-extrabold text-[color:var(--clinical-accent)] disabled:cursor-not-allowed disabled:opacity-50"
>
Compact
</button>
<button
type="button"
disabled
className="min-h-9 rounded-md px-3 text-xs font-bold text-[color:var(--text-muted)] disabled:cursor-not-allowed disabled:opacity-50"
>
Detailed
</button>
</div>
<span
id="density-controls-unavailable"
className="px-1 text-2xs font-bold uppercase tracking-[0.08em] text-[color:var(--text-soft)]"
>
Detailed
</button>
<span id="presentation-density-unavailable" className="sr-only">
Compact and detailed density controls are coming soon.
Density controls coming soon
</span>
</div>
</div>
Expand Down
53 changes: 53 additions & 0 deletions tests/answer-summary-mode-contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";

import { answerRequestSchema } from "@/lib/validation/answer-request";

const nonStreamRouteSource = readFileSync(new URL("../src/app/api/answer/route.ts", import.meta.url), "utf8");
const streamRouteSource = readFileSync(new URL("../src/app/api/answer/stream/route.ts", import.meta.url), "utf8");

describe("answer summaryMode contract", () => {
const documentId = "11111111-1111-4111-8111-111111111111";
const otherDocumentId = "22222222-2222-4222-8222-222222222222";

it("rejects conflicting or multi-document summary scopes at request validation", () => {
expect(
answerRequestSchema.safeParse({
query: "Summarize this document.",
documentId,
documentIds: [otherDocumentId],
summaryMode: true,
}).success,
).toBe(false);

expect(
answerRequestSchema.safeParse({
query: "Summarize this document.",
documentId,
documentIds: [documentId, otherDocumentId],
summaryMode: true,
}).success,
).toBe(false);

expect(
answerRequestSchema.safeParse({
query: "Summarize this document.",
documentId,
documentIds: [documentId],
summaryMode: true,
}).success,
).toBe(true);
});

it("keeps non-stream summaryMode rejected before backend answer work", () => {
const rejectionIndex = nonStreamRouteSource.indexOf("summary_mode_stream_required");
expect(rejectionIndex).toBeGreaterThan(0);
expect(nonStreamRouteSource.indexOf("createAdminClient()", rejectionIndex)).toBeGreaterThan(rejectionIndex);
expect(nonStreamRouteSource.indexOf("answerQuestionWithScope({", rejectionIndex)).toBeGreaterThan(rejectionIndex);
});

it("scopes streamed summaryMode to the exact selected document", () => {
expect(streamRouteSource).toContain("body.summaryMode && body.documentId");
expect(streamRouteSource).toContain("? [body.documentId]");
});
});
21 changes: 12 additions & 9 deletions tests/mobile-interaction-regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,22 @@ describe("mobile interaction regressions", () => {
);
expect(presentationSource).toContain("Comparing ({workflow.selectedCount})");
expect(presentationSource).not.toContain("Compare ({workflow.selectedCount} selected)");
// Scope each density control independently so Compact's disabled attrs cannot
// greedily satisfy the Detailed assertion (or vice versa).
// Density placeholders use native disabled only (no redundant aria-disabled).
// Scope Compact/Detailed independently so one button cannot satisfy both asserts.
expect(presentationSource).toContain('aria-describedby="density-controls-unavailable"');
expect(presentationSource).toContain("Density controls coming soon");
expect(presentationSource).toMatch(
/<button\s+type="button"\s+disabled\s+aria-disabled="true"\s+aria-describedby="presentation-density-unavailable"[\s\S]*?>\s*Compact\s*<\/button>/,
/<button\s+type="button"\s+disabled\s+className="[^"]*"\s*>\s*Compact\s*<\/button>/,
);
expect(presentationSource).toMatch(
/<button\s+type="button"\s+disabled\s+aria-disabled="true"\s+aria-describedby="presentation-density-unavailable"[\s\S]*?>\s*Detailed\s*<\/button>/,
/<button\s+type="button"\s+disabled\s+className="[^"]*"\s*>\s*Detailed\s*<\/button>/,
);
expect(
presentationSource.match(
/type="button"\s+disabled\s+aria-disabled="true"\s+aria-describedby="presentation-density-unavailable"/g,
),
).toHaveLength(2);
const densityButtonBlock = presentationSource.match(
/aria-describedby="density-controls-unavailable"[\s\S]*?Density controls coming soon/,
)?.[0];
expect(densityButtonBlock).toBeTruthy();
expect(densityButtonBlock).not.toContain("aria-disabled");
expect(densityButtonBlock?.match(/type="button"\s+disabled/g)).toHaveLength(2);
});

it("does not fake Add success or Tools sort/more menus", () => {
Expand Down
Loading