fix: harden reviewer workflow and memory handling

This commit is contained in:
2026-07-23 12:34:23 +02:00
parent 00761ae2ca
commit 694b7dd21f
23 changed files with 728 additions and 75 deletions
@@ -46,6 +46,56 @@ test("uses memory-specific selection copy without changing selected ids", async
expect(onRespond).toHaveBeenCalledWith({ id: "u-memory", kind: "multiselect", choices: ["recommended"] });
});
test("shows the exact content and provenance for every memory option", () => {
render(
<MultiselectWidget
descriptor={{
id: "u-memory-content",
widget: "multiselect",
options: [
{
id: "mem-42",
label: "paziente attivo",
detail: "flag_attivo = TRUE",
rationale: "Confermato dal reviewer",
meta: { question_context: "Conta i pazienti attivi" },
},
],
}}
onRespond={vi.fn()}
/>
);
expect(screen.getByText("flag_attivo = TRUE")).toBeInTheDocument();
expect(screen.getByText(/Confermato dal reviewer/)).toBeInTheDocument();
expect(screen.getByText(/Conta i pazienti attivi/)).toBeInTheDocument();
});
test("renders memory detail as human-friendly Markdown", () => {
render(
<MultiselectWidget
descriptor={{
id: "u-memory-markdown",
widget: "multiselect",
selection_label: "memory da applicare",
options: [
{
id: "mem-0042",
label: "Paziente attivo",
detail: "**Definizione**\n\n- `flag_attivo = TRUE`\n- record non annullato",
},
],
}}
onRespond={vi.fn()}
/>
);
expect(screen.getByText("Definizione").tagName).toBe("STRONG");
expect(screen.getByText("flag_attivo = TRUE").tagName).toBe("CODE");
expect(screen.getByText("record non annullato").closest("li")).not.toBeNull();
expect(screen.queryByText("**Definizione**")).not.toBeInTheDocument();
});
test("select-all checks all options", async () => {
const onRespond = vi.fn();
render(
+20 -1
View File
@@ -1,4 +1,6 @@
import { useState } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import type { WidgetProps } from "./types";
import { ReservedControls } from "./ReservedControls";
@@ -56,7 +58,24 @@ export function MultiselectWidget({ descriptor, onRespond }: WidgetProps) {
checked={checked.has(o.id)}
onChange={() => toggle(o.id)}
/>
<span>{o.label}</span>
<span className="min-w-0 flex-1">
<span className="block font-medium">{o.label}</span>
{o.detail && (
<div className="thot-prose mt-1 text-sm text-muted-foreground">
<ReactMarkdown remarkPlugins={[remarkGfm]}>{o.detail}</ReactMarkdown>
</div>
)}
{o.rationale && (
<span className="mt-1 block text-xs text-foreground/80">
Motivo: {o.rationale}
</span>
)}
{typeof o.meta?.question_context === "string" && o.meta.question_context && (
<span className="mt-1 block text-xs text-muted-foreground">
Domanda di contesto: {o.meta.question_context}
</span>
)}
</span>
</label>
))}
</div>
@@ -33,6 +33,13 @@ const descriptor: WidgetDescriptor = {
reserved: ["back", "exit", "other"],
};
test("starts include rows checked and exclude rows unchecked even for legacy payloads", () => {
render(<SchemaLinkingGateWidget descriptor={descriptor} onRespond={vi.fn()} />);
expect(screen.getByRole("checkbox", { name: "dim_patient" })).toBeChecked();
expect(screen.getByRole("checkbox", { name: "fact_sostituzione" })).not.toBeChecked();
});
test("Confirm emits enacted tables with suggested columns (catalog order)", async () => {
const onRespond = vi.fn();
render(<SchemaLinkingGateWidget descriptor={descriptor} onRespond={onRespond} />);
@@ -42,7 +49,7 @@ test("Confirm emits enacted tables with suggested columns (catalog order)", asyn
kind: "schema-linking",
tables: [
{ id: "t-pat", enacted: true, columns: ["cod_paz"] },
{ id: "x-sub", enacted: true },
{ id: "x-sub", enacted: false },
],
});
});
@@ -60,7 +67,7 @@ test("selecting a column in the modal adds it to the response", async () => {
kind: "schema-linking",
tables: [
{ id: "t-pat", enacted: true, columns: ["cod_paz", "nome"] },
{ id: "x-sub", enacted: true },
{ id: "x-sub", enacted: false },
],
});
});
@@ -75,7 +82,7 @@ test("declining a table's enact checkbox drops its columns", async () => {
kind: "schema-linking",
tables: [
{ id: "t-pat", enacted: false },
{ id: "x-sub", enacted: true },
{ id: "x-sub", enacted: false },
],
});
});
@@ -7,7 +7,7 @@ import { ReservedControls } from "./ReservedControls";
export function SchemaLinkingGateWidget({ descriptor, onRespond }: WidgetProps) {
const tables: SchemaTable[] = descriptor.tables ?? [];
const [enacted, setEnacted] = useState<Set<string>>(
() => new Set(tables.filter((t) => t.recommended).map((t) => t.id))
() => new Set(tables.filter((t) => t.kind === "promote" && t.recommended).map((t) => t.id))
);
const [selected, setSelected] = useState<Map<string, Set<string>>>(() => {
const m = new Map<string, Set<string>>();