M31: verify RAG inventory and polish attention queue
This commit is contained in:
@@ -233,25 +233,28 @@ test.describe("MO-016 status conflict is order-independent", () => {
|
||||
});
|
||||
|
||||
test.describe("knowledge base is grounded in the operator's own language", () => {
|
||||
const cases: { lang: string; question: string; sourceHint: RegExp }[] = [
|
||||
const cases: { lang: string; question: string; sourceHint: RegExp; sourceTitle: string }[] = [
|
||||
{
|
||||
lang: "nl-BE",
|
||||
question: "Wat moet ik doen wanneer een voertuig beschadigd terugkomt?",
|
||||
sourceHint: /zichtbare of gemelde schade/i,
|
||||
sourceTitle: "Procedure schadeafhandeling",
|
||||
},
|
||||
{
|
||||
lang: "en-GB",
|
||||
question: "What should I do when a vehicle returns with damage?",
|
||||
sourceHint: /visible or reported damage/i,
|
||||
sourceTitle: "Damage handling procedure",
|
||||
},
|
||||
{
|
||||
lang: "fr-BE",
|
||||
question: "Que dois-je faire lorsqu'un véhicule revient endommagé ?",
|
||||
sourceHint: /dommages visibles ou signalés/i,
|
||||
sourceTitle: "Procédure de gestion des dommages",
|
||||
},
|
||||
];
|
||||
|
||||
for (const { lang, question, sourceHint } of cases) {
|
||||
for (const { lang, question, sourceHint, sourceTitle } of cases) {
|
||||
test(`grounded ${lang} answer cites a ${lang} source about damage`, async ({ page, request }) => {
|
||||
await resetDemoData(request);
|
||||
await loginAsOpsManager(page, lang);
|
||||
@@ -260,7 +263,7 @@ test.describe("knowledge base is grounded in the operator's own language", () =>
|
||||
await page.getByRole("button", { name: /^(Vraag stellen|Ask|Demander)$/ }).click();
|
||||
const localizedSource = page.locator(".source-card", { hasText: sourceHint }).first();
|
||||
await expect(localizedSource).toBeVisible({ timeout: 15_000 });
|
||||
await expect(localizedSource).toContainText(/Schade bij voertuigretour|Vehicle return damage|Dommages au retour du véhicule/i);
|
||||
await expect(localizedSource).toContainText(sourceTitle);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
async function loginAsOperationsManager(page: Page) {
|
||||
await page.goto("/login");
|
||||
await page.getByRole("button", { name: "Verken als Operationsmanager" }).click();
|
||||
await expect(page).toHaveURL(/\/dashboard$/);
|
||||
}
|
||||
|
||||
test("90-second recruiter entry exposes three verifiable engineering highlights", async ({ page }) => {
|
||||
await page.goto("/login");
|
||||
await page.getByRole("button", { name: "Bekijk de highlights in 90 seconden" }).click();
|
||||
@@ -14,8 +20,7 @@ test("90-second recruiter entry exposes three verifiable engineering highlights"
|
||||
});
|
||||
|
||||
test("Engineering Story exposes architecture, reliability and honest integration evidence", async ({ page }) => {
|
||||
await page.goto("/login");
|
||||
await page.getByRole("button", { name: "Verken als Operationsmanager" }).click();
|
||||
await loginAsOperationsManager(page);
|
||||
await page.goto("/about");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Controle, betrouwbaarheid en uitlegbaarheid" }).first()).toBeVisible();
|
||||
@@ -36,3 +41,44 @@ test("mobile recruiter surfaces remain readable without horizontal overflow", as
|
||||
expect(overflow).toBeLessThanOrEqual(1);
|
||||
await expect(page.locator(".highlight-card").first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("remaining attention items use a compact, legible queue action", async ({ page, request }) => {
|
||||
await request.post("/api/v1/demo/login", { data: { role: "operations_manager" } });
|
||||
await request.post("/api/v1/demo/reset");
|
||||
await loginAsOperationsManager(page);
|
||||
|
||||
const queueAction = page.locator(".queue-more a");
|
||||
await expect(queueAction).toContainText("Nog 2 aandachtspunten");
|
||||
await expect(queueAction).toContainText("Open de volledige werklijst");
|
||||
const actionBox = await queueAction.boundingBox();
|
||||
const arrowBox = await queueAction.locator(".queue-more-arrow svg").boundingBox();
|
||||
expect(actionBox?.height).toBeLessThanOrEqual(70);
|
||||
expect(arrowBox?.width).toBeLessThanOrEqual(16);
|
||||
});
|
||||
|
||||
test("Knowledge Hub renders independently verified RAGcore index evidence", async ({ page }) => {
|
||||
await loginAsOperationsManager(page);
|
||||
await page.route("**/api/v1/knowledge/status**", async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
provider: "ragcore",
|
||||
available: true,
|
||||
detail: "11/11 managed sources verified",
|
||||
tenant: "northstar-mobility-demo",
|
||||
workspace: "mobilityops",
|
||||
collection: "internal-procedures",
|
||||
document_count: 11,
|
||||
source_document_count: 11,
|
||||
reported_synced_document_count: 33,
|
||||
reported_failed_document_count: 0,
|
||||
last_sync_at: "2026-08-10T16:00:00Z",
|
||||
statistics_state: "verified",
|
||||
}),
|
||||
});
|
||||
});
|
||||
await page.goto("/knowledge");
|
||||
|
||||
await expect(page.getByText("11 procedures geïndexeerd")).toBeVisible();
|
||||
await expect(page.getByText("actief gepubliceerd en inhoudelijk geverifieerd")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -36,7 +36,9 @@
|
||||
"title": "Attention queue",
|
||||
"description": "{{openIssues}} open quality issues · {{workflowExceptions}} workflow exceptions",
|
||||
"reviewQueue": "Review queue",
|
||||
"remaining": "View {{count}} more attention item(s)",
|
||||
"remaining_one": "One more attention item",
|
||||
"remaining_other": "{{count}} more attention items",
|
||||
"remainingHint": "Open the complete work queue",
|
||||
"filterPlaceholder": "Filter issues…",
|
||||
"filterAriaLabel": "Search attention queue",
|
||||
"severityAll": "All severity",
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
"checkout-procedure": "Required checkout controls"
|
||||
},
|
||||
"statistics": {
|
||||
"verifiedIndexed": "actively published and content-verified",
|
||||
"sourceDocuments": "source documents in this language",
|
||||
"reportedSynced": "sync reported by n8n",
|
||||
"reportedFailed": "sync failures reported",
|
||||
"lastSync": "Latest sync report: {{when}}. RAGcore does not expose a verifiable index size, so sync counts are not presented as indexed documents.",
|
||||
"noSyncReport": "No sync report has been received yet. RAGcore does not expose a verifiable index size."
|
||||
"lastSync": "Latest sync report: {{when}}. Index state is independently checked against each managed source's active RAGcore version and content hash.",
|
||||
"noSyncReport": "No sync report has been received yet. Index evidence is checked separately through active RAGcore versions."
|
||||
},
|
||||
"providerNote": "This demo answers from a small, fixed set of indexed procedures — not a live RAGcore connection. A live RAGcore backend will later take over the same interface without changing how this page works.",
|
||||
"askHeading": "Ask a procedure question",
|
||||
|
||||
@@ -36,7 +36,9 @@
|
||||
"title": "File d'attention",
|
||||
"description": "{{openIssues}} problèmes de qualité ouverts · {{workflowExceptions}} exceptions de workflow",
|
||||
"reviewQueue": "Examiner la file",
|
||||
"remaining": "Voir encore {{count}} point(s) d’attention",
|
||||
"remaining_one": "Encore un point d’attention",
|
||||
"remaining_other": "Encore {{count}} points d’attention",
|
||||
"remainingHint": "Ouvrir la file de travail complète",
|
||||
"filterPlaceholder": "Filtrer les problèmes…",
|
||||
"filterAriaLabel": "Rechercher dans la file d'attention",
|
||||
"severityAll": "Toute gravité",
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
"checkout-procedure": "Contrôles obligatoires au départ"
|
||||
},
|
||||
"statistics": {
|
||||
"verifiedIndexed": "publiées activement et contenu vérifié",
|
||||
"sourceDocuments": "documents sources dans cette langue",
|
||||
"reportedSynced": "synchronisation signalée par n8n",
|
||||
"reportedFailed": "échecs de synchronisation signalés",
|
||||
"lastSync": "Dernier rapport de synchronisation : {{when}}. RAGcore n’expose pas de taille d’index vérifiable ; les nombres synchronisés ne sont donc pas présentés comme des documents indexés.",
|
||||
"noSyncReport": "Aucun rapport de synchronisation reçu. RAGcore n’expose pas de taille d’index vérifiable."
|
||||
"lastSync": "Dernier rapport de synchronisation : {{when}}. L’état de l’index est contrôlé indépendamment via la version RAGcore active et l’empreinte de contenu de chaque source gérée.",
|
||||
"noSyncReport": "Aucun rapport de synchronisation reçu. Les preuves d’indexation sont contrôlées séparément via les versions RAGcore actives."
|
||||
},
|
||||
"providerNote": "Cette démo répond à partir d'un petit ensemble fixe de procédures indexées — pas d'une connexion RAGcore en direct. Un backend RAGcore en direct reprendra plus tard la même interface sans changer le fonctionnement de cette page.",
|
||||
"askHeading": "Poser une question de procédure",
|
||||
|
||||
@@ -36,7 +36,9 @@
|
||||
"title": "Aandachtspunten",
|
||||
"description": "{{openIssues}} open datakwaliteitsproblemen · {{workflowExceptions}} automatiseringsuitzonderingen",
|
||||
"reviewQueue": "Wachtrij bekijken",
|
||||
"remaining": "Nog {{count}} aandachtspunt(en) bekijken",
|
||||
"remaining_one": "Nog één aandachtspunt",
|
||||
"remaining_other": "Nog {{count}} aandachtspunten",
|
||||
"remainingHint": "Open de volledige werklijst",
|
||||
"filterPlaceholder": "Filter aandachtspunten…",
|
||||
"filterAriaLabel": "Zoek in aandachtspunten",
|
||||
"severityAll": "Alle ernst",
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
"checkout-procedure": "Verplichte vertrekcontroles"
|
||||
},
|
||||
"statistics": {
|
||||
"verifiedIndexed": "actief gepubliceerd en inhoudelijk geverifieerd",
|
||||
"sourceDocuments": "brondocumenten in deze taal",
|
||||
"reportedSynced": "sync door n8n gerapporteerd",
|
||||
"reportedFailed": "syncfouten gerapporteerd",
|
||||
"lastSync": "Laatste syncrapport: {{when}}. RAGcore stelt geen verifieerbare indexomvang beschikbaar; syncaantallen worden daarom niet als geïndexeerde documenten voorgesteld.",
|
||||
"noSyncReport": "Nog geen syncrapport ontvangen. RAGcore stelt geen verifieerbare indexomvang beschikbaar."
|
||||
"lastSync": "Laatste syncrapport: {{when}}. De indexstand wordt onafhankelijk gecontroleerd via de actieve RAGcore-versie en inhoudshash van elke beheerde bron.",
|
||||
"noSyncReport": "Nog geen syncrapport ontvangen. Indexbewijs wordt afzonderlijk via de actieve RAGcore-versies gecontroleerd."
|
||||
},
|
||||
"providerNote": "Deze demo beantwoordt vanuit een kleine, vaste set geïndexeerde procedures — geen live RAGcore-koppeling. Een live RAGcore-backend zal later dezelfde interface overnemen, zonder dat deze pagina verandert.",
|
||||
"askHeading": "Stel een procedurevraag",
|
||||
|
||||
@@ -190,7 +190,16 @@ export function Dashboard() {
|
||||
</div>
|
||||
))}
|
||||
{isUnfiltered && attention.length > 6 && canSeeQuality && (
|
||||
<p className="queue-more"><Link to="/data-quality">{t("attention.remaining", { count: attention.length - 6 })} <Icon name="chevron" /></Link></p>
|
||||
<p className="queue-more">
|
||||
<Link to="/data-quality">
|
||||
<span className="queue-more-count" aria-hidden="true">{attention.length - 6}</span>
|
||||
<span className="queue-more-copy">
|
||||
<strong>{t("attention.remaining", { count: attention.length - 6 })}</strong>
|
||||
<small>{t("attention.remainingHint")}</small>
|
||||
</span>
|
||||
<span className="queue-more-arrow"><Icon name="chevron" /></span>
|
||||
</Link>
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -154,6 +154,7 @@ export function Knowledge() {
|
||||
)}
|
||||
{statusSettled && status?.provider === "ragcore" && (
|
||||
<div className="knowledge-index-evidence" role="status">
|
||||
<div><strong>{status.document_count ?? "—"}</strong><span>{t("statistics.verifiedIndexed")}</span></div>
|
||||
<div><strong>{status.source_document_count}</strong><span>{t("statistics.sourceDocuments")}</span></div>
|
||||
<div>
|
||||
<strong>{status.reported_synced_document_count ?? "—"}</strong>
|
||||
|
||||
+12
-3
@@ -182,7 +182,7 @@ a:hover { color: var(--teal); }
|
||||
.inline-action { display: inline-flex; align-items: center; gap: 5px; padding: 0 18px; color: var(--teal-dark); text-decoration: none; font-size: .72rem; font-weight: 700; white-space: nowrap; }
|
||||
.inline-action svg { width: 14px; }
|
||||
|
||||
.operations-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(320px, .75fr); gap: 16px; margin-bottom: 16px; }
|
||||
.operations-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(320px, .75fr); align-items: start; gap: 16px; margin-bottom: 16px; }
|
||||
.secondary-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
.work-panel, .panel, .record-surface, .table-shell { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
|
||||
.panel { padding: 20px; }
|
||||
@@ -195,7 +195,15 @@ a:hover { color: var(--teal); }
|
||||
.attention-list, .integration-list, .recent-list, .record-list, .automation-list, .today-list { list-style: none; margin: 0; padding: 0; }
|
||||
.attention-list li { min-height: 68px; display: grid; grid-template-columns: auto minmax(0,1fr) auto 16px; align-items: center; gap: 11px; padding: 10px 18px; border-bottom: 1px solid #e8edf2; transition: background .14s ease; }
|
||||
.attention-list li:last-child { border-bottom: 0; }.attention-list li:hover { background: #f9fbfc; }
|
||||
.queue-more { margin: 0; padding: 13px 18px; border-top: 1px solid var(--line); font-size: var(--type-meta); font-weight: 700; }.queue-more a { display: inline-flex; align-items: center; gap: 5px; }
|
||||
.queue-more { margin: 0; padding: 10px 12px; border-top: 1px solid var(--line); background: var(--surface-subtle); }
|
||||
.queue-more a { display: grid; grid-template-columns: 34px minmax(0, 1fr) 30px; align-items: center; gap: 10px; min-height: 52px; padding: 7px 8px; color: var(--ink); background: white; border: 1px solid var(--line); border-radius: var(--radius); text-decoration: none; transition: border-color .14s ease, box-shadow .14s ease, transform .14s ease; }
|
||||
.queue-more a:hover { border-color: var(--teal); box-shadow: 0 5px 16px rgba(13, 126, 116, .09); transform: translateY(-1px); }
|
||||
.queue-more-count { width: 34px; height: 34px; display: grid; place-items: center; color: var(--teal-dark); background: var(--teal-pale); border-radius: 50%; font-size: .78rem; font-weight: 800; }
|
||||
.queue-more-copy { min-width: 0; display: grid; gap: 2px; }
|
||||
.queue-more-copy strong { font-size: .75rem; line-height: 1.25; }
|
||||
.queue-more-copy small { color: var(--muted); font-size: .64rem; font-weight: 500; }
|
||||
.queue-more-arrow { width: 30px; height: 30px; display: grid; place-items: center; color: var(--teal-dark); background: var(--teal-pale); border-radius: 50%; }
|
||||
.queue-more-arrow svg { width: 14px; height: 14px; }
|
||||
.attention-title { margin: 0; font-size: .78rem; font-weight: 700; }.attention-title a { color: var(--ink); text-decoration: none; }
|
||||
.attention-detail { max-width: 58ch; margin: 4px 0 0; color: var(--muted); font-size: .69rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.queue-ref { color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .65rem; }.row-chevron { width: 14px; color: var(--muted-light); }
|
||||
@@ -475,11 +483,12 @@ details summary { cursor: pointer; color: var(--teal-dark); }.data-table details
|
||||
|
||||
.knowledge-provider-note { display: flex; align-items: flex-start; gap: 8px; margin: -6px 0 18px; padding: 10px 14px; color: var(--ink-soft); background: var(--info-pale); border: 1px solid #cfe3ee; border-radius: var(--radius); font-size: .72rem; line-height: 1.5; }
|
||||
.knowledge-provider-note svg { width: 15px; flex-shrink: 0; margin-top: 1px; color: var(--info); }
|
||||
.knowledge-index-evidence { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; margin: -6px 0 18px; overflow: hidden; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius); }
|
||||
.knowledge-index-evidence { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; margin: -6px 0 18px; overflow: hidden; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius); }
|
||||
.knowledge-index-evidence > div { display: grid; gap: 3px; padding: 11px 14px; background: white; }
|
||||
.knowledge-index-evidence strong { font-size: .86rem; }
|
||||
.knowledge-index-evidence span, .knowledge-index-evidence p { color: var(--muted); font-size: .66rem; line-height: 1.45; }
|
||||
.knowledge-index-evidence p { grid-column: 1 / -1; margin: 0; padding: 9px 14px; background: var(--surface-subtle); }
|
||||
@media (max-width: 640px) { .knowledge-index-evidence { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
.knowledge-suggestions { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-top: 12px; }
|
||||
.knowledge-suggestions > span { color: var(--muted); font-size: .68rem; font-weight: 700; }
|
||||
.suggestion-chip { padding: 6px 11px; color: var(--teal-dark); background: var(--teal-pale); border: 1px solid #bfe6df; border-radius: 999px; font-size: .68rem; font-weight: 600; cursor: pointer; }
|
||||
|
||||
Reference in New Issue
Block a user