From ef8414b02275094d6ba47316c01e2f51fe19f87e Mon Sep 17 00:00:00 2001 From: ignamiranda <34501347+ignamiranda@users.noreply.github.com> Date: Sun, 23 Aug 2026 05:44:29 -0400 Subject: [PATCH] Beginner UX: interpret health status with plain-language verdict (#11224) Validated on the combined batch board (health-page static test green, new testing-library deps installed for the component test) + this branch: i18n suites 16/16 with Vietnamese translations of the verdict/diagnostics strings added here. Plain-language verdict leads; advanced diagnostics collapse behind a toggle. Thank you @ignamiranda! --- package-lock.json | 95 +++++++++++++++++++ package.json | 2 + src/app/(dashboard)/dashboard/health/page.tsx | 43 +++++++-- src/i18n/messages/en.json | 6 ++ src/i18n/messages/vi.json | 8 +- tests/unit/health-page-static.test.ts | 29 ++++++ 6 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 tests/unit/health-page-static.test.ts diff --git a/package-lock.json b/package-lock.json index bf77bab602..e30c2088b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,8 +104,10 @@ "@stryker-mutator/core": "^10.0.0", "@stryker-mutator/tap-runner": "^10.0.0", "@tailwindcss/postcss": "^4.3.0", + "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.6", "@types/better-sqlite3": "^9.6.0", "@types/bun": "latest", "@types/node": "^26.2.0", @@ -12178,6 +12180,26 @@ "tailwindcss": "4.3.3" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@testing-library/jest-dom": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.1.tgz", @@ -12242,6 +12264,20 @@ } } }, + "node_modules/@testing-library/user-event": { + "version": "14.6.6", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.6.tgz", + "integrity": "sha512-Jbs9FpkkIDw8FgSc6kOVsOv8JuuqGAL7J4X1oot77JxAoDlkNn2GRkd0aYRVuQ+pVQAiHWVkE4rX/dkF5fBiCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tokenizer/inflate": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", @@ -12326,6 +12362,13 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/better-sqlite3": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-9.6.0.tgz", @@ -18138,6 +18181,13 @@ "dev": true, "license": "MIT" }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dompurify": { "version": "3.4.13", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", @@ -26532,6 +26582,16 @@ "node": ">=12" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -31360,6 +31420,41 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/pretty-ms": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", diff --git a/package.json b/package.json index 474e44de95..cf08e93b61 100644 --- a/package.json +++ b/package.json @@ -358,8 +358,10 @@ "@stryker-mutator/core": "^10.0.0", "@stryker-mutator/tap-runner": "^10.0.0", "@tailwindcss/postcss": "^4.3.0", + "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.6", "@types/better-sqlite3": "^9.6.0", "@types/bun": "latest", "@types/node": "^26.2.0", diff --git a/src/app/(dashboard)/dashboard/health/page.tsx b/src/app/(dashboard)/dashboard/health/page.tsx index fe5e1fe144..3fe237ee44 100644 --- a/src/app/(dashboard)/dashboard/health/page.tsx +++ b/src/app/(dashboard)/dashboard/health/page.tsx @@ -13,6 +13,7 @@ */ import { useState, useEffect, useCallback } from "react"; + import { Card } from "@/shared/components"; import { AI_PROVIDERS } from "@/shared/constants/providers"; import { getProviderDisplayName } from "@/lib/display/names"; @@ -74,6 +75,7 @@ export default function HealthPage() { const [repairingDb, setRepairingDb] = useState(false); const [unblocking, setUnblocking] = useState(false); const [unblockingKey, setUnblockingKey] = useState(null); + const [showAdvanced, setShowAdvanced] = useState(false); const fetchHealth = useCallback(async () => { try { @@ -266,6 +268,21 @@ export default function HealthPage() { {/* Status Banner */} + {/* Verdict Header */} +
+

+ { + data.status === "healthy" + ? t("healthVerdictReady") + : data.status === "cooling" + ? t("healthVerdictCoolingDown") + : t("healthVerdictActionRequired") + } +

+

{t("healthSubtitle")}

+
+ + {/* Status Details */}
- {data.status === "healthy" ? t("allOperational") : t("issuesDetected")} + {data.status === "healthy" + ? t("allOperational") + : t("issuesDetected")}
- - - - - + {/* Advanced Diagnostics Section */} +
+
+

{t("advancedDiagnosticsTitle")}

+ +
+
+ + + +
+
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 914d5bcc6b..7bafe22203 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1214,6 +1214,12 @@ "consoleLogsSubtitle": "Console output", "logsActivitySubtitle": "User activity log", "healthSubtitle": "System health check", + "healthVerdictReady": "OmniRoute is ready", + "healthVerdictActionRequired": "Action required to restore full operation", + "healthVerdictCoolingDown": "Cooling down after recent changes", + "advancedDiagnosticsTitle": "Advanced diagnostics", + "hide": "Hide", + "show": "Show", "costsPricingSubtitle": "Per-model pricing rules", "costsBudgetSubtitle": "Budget limits", "costsQuotaShareSubtitle": "Share provider quotas across keys", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index fa84fa5d7a..e049342cfb 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -1293,7 +1293,13 @@ "open": "mở", "close": "đóng" }, - "noResults": "Không có kết quả" + "noResults": "Không có kết quả", + "healthVerdictReady": "OmniRoute đã sẵn sàng", + "healthVerdictActionRequired": "Cần hành động để khôi phục hoạt động đầy đủ", + "healthVerdictCoolingDown": "Đang nguội sau các thay đổi gần đây", + "advancedDiagnosticsTitle": "Chẩn đoán nâng cao", + "hide": "Ẩn", + "show": "Hiện" }, "webhooks": { "title": "Webhook", diff --git a/tests/unit/health-page-static.test.ts b/tests/unit/health-page-static.test.ts new file mode 100644 index 0000000000..8520901ad5 --- /dev/null +++ b/tests/unit/health-page-static.test.ts @@ -0,0 +1,29 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const pagePath = path.join(repoRoot, "src/app/(dashboard)/dashboard/health/page.tsx"); + +function readPage() { + return fs.readFileSync(pagePath, "utf8"); +} + +test("health page leads with a plain-language verdict and a collapsible advanced section", () => { + const source = readPage(); + + // Verdict header with plain-language states + assert.match(source, /healthVerdictReady/); + assert.match(source, /healthVerdictCoolingDown/); + assert.match(source, /healthVerdictActionRequired/); + + // No hardcoded English outcomes in the verdict header + assert.doesNotMatch(source, /OmniRoute is ready/); + + // Collapsible "Advanced diagnostics" section + assert.match(source, /advancedDiagnosticsTitle/); + assert.match(source, /setShowAdvanced/); + assert.match(source, /showAdvanced \? t\("hide"\) : t\("show"\)/); +});