From 017cd24320184bbbea4066431085eaeec7254d09 Mon Sep 17 00:00:00 2001 From: ignamiranda Date: Sun, 23 Aug 2026 03:45:08 -0400 Subject: [PATCH 1/8] feat(endpoint): guided connection header with endpoint URL --- .../dashboard/endpoint/EndpointPageClient.tsx | 14 +++++++++++++- src/i18n/messages/en.json | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx index a670f9cc34ad..f52110328702 100644 --- a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx +++ b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx @@ -2360,7 +2360,19 @@ function ProviderModelsModal({
{groupModels.map((m) => { const copyKey = `modal-${m.id}`; - return ( + return ( +
+
+

{t("endpoint.title")}

+

{t("endpoint.subtitle")}

+
+ {useDisplayBaseUrl()}/v1 + {t("endpoint.testEndpoint")} +
+
+
+ {t("endpoint.advancedProtocols")} +
Date: Sun, 23 Aug 2026 05:35:08 -0400 Subject: [PATCH 2/8] docs(i18n): clarify embedded services purpose for beginners (#11204) Validated on the combined batch board: gates clean (changelog, file-size 159 frozen, complexity 2620<=2774, cognitive 1180<=1223, dead-code 409<=416), typecheck:core clean, i18n suites 16/16 after the vi translation pass. Two-string reframe that stops Embedded Services reading as required infrastructure. Thank you @ignamiranda! --- src/i18n/messages/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 930f5b595875..4cc06f10d325 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1012,7 +1012,7 @@ "apiManager": "API Keys", "apiManagerSubtitle": "Manage API keys and access", "embeddedServices": "Embedded Services", - "embeddedServicesSubtitle": "Manage local proxy services", + "embeddedServicesSubtitle": "Optional local services", "logs": "Logs", "webhooks": "Webhooks", "webhooksSubtitle": "Get notified of events", @@ -8677,7 +8677,7 @@ }, "embeddedServices": { "title": "Embedded Services", - "description": "Local engines managed on demand — CLIProxyAPI, 9Router, Mux, and Bifrost. Accessible on loopback only.", + "description": "Optional helpers that run on your machine. Most users can start without them — install one only when an integration needs it.", "stateRunning": "Running", "stateStopped": "Stopped", "stateStarting": "Starting", From c1e9fbf279efc1a58302bfffeb7f418bd9ea77ca Mon Sep 17 00:00:00 2001 From: ignamiranda <34501347+ignamiranda@users.noreply.github.com> Date: Sun, 23 Aug 2026 05:39:23 -0400 Subject: [PATCH 3/8] feat(dashboard): orient users before API key management (#11195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated on the combined batch board: gates + typecheck clean, i18n suites 16/16. Pre-merge: the vi.json request-flow strings arrived in English — translated to Vietnamese on the branch (vi is the locale that requires real translations, not markers). Purpose-first API-key orientation with the request-path visual, controls unchanged. Part of #11167. Thank you @ignamiranda! --- .../api-manager/ApiManagerPageClient.tsx | 38 +++++++++++++++++++ src/i18n/messages/ar.json | 4 ++ src/i18n/messages/az.json | 4 ++ src/i18n/messages/bg.json | 4 ++ src/i18n/messages/bn.json | 4 ++ src/i18n/messages/cs.json | 4 ++ src/i18n/messages/da.json | 4 ++ src/i18n/messages/de.json | 4 ++ src/i18n/messages/en.json | 4 ++ src/i18n/messages/es.json | 4 ++ src/i18n/messages/fa.json | 4 ++ src/i18n/messages/fi.json | 4 ++ src/i18n/messages/fr.json | 4 ++ src/i18n/messages/gu.json | 4 ++ src/i18n/messages/he.json | 4 ++ src/i18n/messages/hi.json | 4 ++ src/i18n/messages/hu.json | 4 ++ src/i18n/messages/id.json | 4 ++ src/i18n/messages/in.json | 4 ++ src/i18n/messages/it.json | 4 ++ src/i18n/messages/ja.json | 4 ++ src/i18n/messages/ko.json | 4 ++ src/i18n/messages/mr.json | 4 ++ src/i18n/messages/ms.json | 4 ++ src/i18n/messages/nl.json | 4 ++ src/i18n/messages/no.json | 4 ++ src/i18n/messages/phi.json | 4 ++ src/i18n/messages/pl.json | 4 ++ src/i18n/messages/pt-BR.json | 4 ++ src/i18n/messages/pt.json | 4 ++ src/i18n/messages/ro.json | 4 ++ src/i18n/messages/ru.json | 4 ++ src/i18n/messages/sk.json | 4 ++ src/i18n/messages/sv.json | 4 ++ src/i18n/messages/sw.json | 4 ++ src/i18n/messages/ta.json | 4 ++ src/i18n/messages/te.json | 4 ++ src/i18n/messages/th.json | 4 ++ src/i18n/messages/tr.json | 4 ++ src/i18n/messages/uk-UA.json | 4 ++ src/i18n/messages/ur.json | 4 ++ src/i18n/messages/vi.json | 4 ++ src/i18n/messages/zh-CN.json | 4 ++ src/i18n/messages/zh-TW.json | 4 ++ tests/unit/api-manager-page-static.test.ts | 20 ++++++++++ 45 files changed, 230 insertions(+) diff --git a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx index 1e6d1db37add..898f475340ff 100644 --- a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx +++ b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx @@ -959,6 +959,44 @@ export default function ApiManagerPageClient() {
)} +
+
+
+

{t("keyManagement")}

+

{t("keyManagementDesc")}

+
+
+ + {t("requestFlowYourApp")} + + + + {t("requestFlowApiKey")} + + + + {t("requestFlowOmniRoute")} + +
+
+ +
+ {/* Filter Bar — shown when there are keys */} {keys.length > 0 && ( { assert.doesNotMatch(managementBlock, /Allow this API key to manage OmniRoute configuration\./); }); +test("API manager page renders purpose-first header", () => { + const source = readApiManagerPage(); + const headerBlock = source.slice( + source.indexOf('

\s*Your app\s*\s*API key\s*\s*OmniRoute\s* { const source = readApiManagerPage(); const expirationBlock = source.slice( From 7e573546a043b574b0e6b612c531452e42f36e57 Mon Sep 17 00:00:00 2001 From: ignamiranda <34501347+ignamiranda@users.noreply.github.com> Date: Sun, 23 Aug 2026 05:40:37 -0400 Subject: [PATCH 4/8] feat(dashboard): frame ACP as optional advanced setup (#11206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated on the combined batch board (CliConceptCard vitest 9/9 — the ACP warning never leaks onto Code/Agent cards) + this branch: i18n suites 16/16 with the Vietnamese translation of the new acp warning added here. ACP now reads as optional/advanced before its mechanics. Thank you @ignamiranda! --- src/i18n/messages/en.json | 3 ++- src/i18n/messages/vi.json | 3 ++- src/shared/components/cli/CliConceptCard.tsx | 24 +++++++++++++------- tests/unit/ui/CliConceptCard.test.tsx | 14 ++++++++++-- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 162323101916..b850f3924d7e 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -12033,7 +12033,8 @@ }, "acp": { "title": "ACP Agents", - "phrase": "CLIs that OmniRoute spawns as execution backend (reverse flow)", + "phrase": "Run complex, long-running logic in the backend", + "warning": "Most users can ignore this — use only when an integration requires it.", "flow": "Client → OmniRoute → spawn CLI (stdio/ACP) → response", "seeOther": "See →" } diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index d066e639d3b2..b9a01f57fc0f 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -12035,7 +12035,8 @@ "title": "Các tác nhân ACP", "phrase": "Các CLI mà OmniRoute khởi chạy làm phần phụ trợ thực thi (luồng ngược)", "flow": "Ứng dụng khách → OmniRoute → khởi chạy CLI (stdio/ACP) → phản hồi", - "seeOther": "See →" + "seeOther": "See →", + "warning": "Hầu hết người dùng có thể bỏ qua — chỉ dùng khi một tích hợp yêu cầu." } }, "comparison": { diff --git a/src/shared/components/cli/CliConceptCard.tsx b/src/shared/components/cli/CliConceptCard.tsx index b6719957ee38..65926c04d66b 100644 --- a/src/shared/components/cli/CliConceptCard.tsx +++ b/src/shared/components/cli/CliConceptCard.tsx @@ -2,6 +2,8 @@ import Link from "next/link"; import { useTranslations } from "next-intl"; + +import Badge from "@/shared/components/Badge"; import { cn } from "@/shared/utils/cn"; export type CliConceptType = "code" | "agent" | "acp"; @@ -23,19 +25,25 @@ export default function CliConceptCard({ currentType }: CliConceptCardProps) { return (
{/* Current type — highlighted */}
- - {t(`concept.${currentType}.title`)} - +
+ + {t(`concept.${currentType}.title`)} + + {currentType === "acp" && ( + + {t("concept.acp.warning")} + + )} +

{t(`concept.${currentType}.phrase`)}

-

{t(`concept.${currentType}.flow`)}

+

+ {t(`concept.${currentType}.flow`)} +

{/* Other types as chips */} diff --git a/tests/unit/ui/CliConceptCard.test.tsx b/tests/unit/ui/CliConceptCard.test.tsx index 8d14b42ac1b0..0821eb64836e 100644 --- a/tests/unit/ui/CliConceptCard.test.tsx +++ b/tests/unit/ui/CliConceptCard.test.tsx @@ -46,8 +46,9 @@ function renderCard(currentType: CliConceptType): HTMLElement { // ── Lifecycle ───────────────────────────────────────────────────────────────── beforeEach(() => { - (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = - true; + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; }); afterEach(() => { @@ -74,8 +75,17 @@ describe("CliConceptCard", () => { it("renders with currentType=acp", () => { const container = renderCard("acp"); expect(container.textContent).toContain("concept.acp.title"); + expect(container.textContent).toContain("concept.acp.warning"); }); + it.each(["code", "agent"] satisfies CliConceptType[])( + "does not show the ACP warning for currentType=%s", + (type) => { + const container = renderCard(type); + expect(container.textContent).not.toContain("concept.acp.warning"); + } + ); + it("for currentType=code, card has primary bg class", () => { const container = renderCard("code"); // The root card div should have primary/5 styling From dfe73f460956e25fbfd535b27f88e2259ceed1bc Mon Sep 17 00:00:00 2001 From: ignamiranda <34501347+ignamiranda@users.noreply.github.com> Date: Sun, 23 Aug 2026 05:41:55 -0400 Subject: [PATCH 5/8] Beginner UX: lead resilience page with plain-language reassurance (#11215) Validated on the combined batch board (resilience-connections static test green) + this branch: i18n suites 16/16 with Vietnamese translations of the reassurance/legend strings added here. Plain-language reassurance + three-state legend lead the page; the technical table stays as advanced detail. Thank you @ignamiranda! --- .../dashboard/resilience/connections/page.tsx | 34 ++++++++++++++++++- src/i18n/messages/en.json | 7 ++++ src/i18n/messages/vi.json | 7 ++++ ...resilience-connections-page-static.test.ts | 20 +++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/app/(dashboard)/dashboard/resilience/connections/page.tsx b/src/app/(dashboard)/dashboard/resilience/connections/page.tsx index d0351813a123..f138d3f5d227 100644 --- a/src/app/(dashboard)/dashboard/resilience/connections/page.tsx +++ b/src/app/(dashboard)/dashboard/resilience/connections/page.tsx @@ -7,7 +7,39 @@ export default async function ResilienceConnectionsPage() { const t = await getTranslations("resilienceConnections"); return (
-

{t("title")}

+
+

{t("title")}

+

+ {t("reassuranceTitle")} {t("reassuranceDetail")} +

+
    +
  • + {t("table.healthy")} — {t("plainStates.healthy")} +
  • +
  • + {t("table.coolingDown")} — {t("plainStates.coolingDown")} +
  • +
  • + {t("table.circuitOpen")} — {t("plainStates.lockedOut")} +
  • +
+
); diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index b850f3924d7e..914d5bcc6baa 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -13342,6 +13342,13 @@ }, "resilienceConnections": { "title": "Connection Resilience", + "reassuranceTitle": "Your connections recover automatically", + "reassuranceDetail": "Usually no action is needed. OmniRoute temporarily rests a connection after failures, then safely tries it again.", + "plainStates": { + "healthy": "Requests can be sent", + "coolingDown": "Trying again soon", + "lockedOut": "Needs your attention" + }, "table": { "status": "Status", "provider": "Provider", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index b9a01f57fc0f..fa84fa5d7ac0 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -13436,6 +13436,13 @@ "modelLockouts": "Khóa Mô Hình", "count": "Số Lượng Kết Nối" } + }, + "reassuranceTitle": "Kết nối của bạn tự khôi phục", + "reassuranceDetail": "Thường không cần hành động. OmniRoute tạm cho kết nối nghỉ sau lỗi, rồi thử lại an toàn.", + "plainStates": { + "healthy": "Có thể gửi yêu cầu", + "coolingDown": "Sắp thử lại", + "lockedOut": "Cần bạn xử lý" } }, "featureFlagCapabilityFilterEnabledDescription": "Từ chối yêu cầu trước khi gửi đi khi mô hình đích thiếu các khả năng bắt buộc (thị giác, công cụ, đầu ra có cấu trúc, cửa sổ ngữ cảnh). Bảo vệ các yêu cầu trực tiếp đến một nhà cung cấp khi chúng bỏ qua bộ lọc tương thích của combo.", diff --git a/tests/unit/resilience-connections-page-static.test.ts b/tests/unit/resilience-connections-page-static.test.ts index 55359d29a9e8..d2ee8be29560 100644 --- a/tests/unit/resilience-connections-page-static.test.ts +++ b/tests/unit/resilience-connections-page-static.test.ts @@ -139,3 +139,23 @@ test("no hardcoded English labels in component files (all via t(...))", () => { assert.doesNotMatch(src, />\s*No connections\s* { + const src = read(componentFiles[0]); + assert.match(src, /reassuranceTitle/, "reassurance heading missing from page.tsx"); + assert.match(src, /reassuranceDetail/, "reassurance detail missing from page.tsx"); + // Plain-language state legend renders before the client component + const legendPos = src.indexOf("plainStates"); + const clientPos = src.indexOf(" -1 && legendPos < clientPos, "state legend must render before the table"); +}); + +test("en.json carries plain-language state copy", () => { + const messages = JSON.parse(read("src/i18n/messages/en.json")); + const block = messages.resilienceConnections; + assert.equal(block.reassuranceTitle, "Your connections recover automatically"); + assert.match(block.reassuranceDetail, /no action is needed/i); + assert.equal(block.plainStates.healthy, "Requests can be sent"); + assert.equal(block.plainStates.coolingDown, "Trying again soon"); + assert.equal(block.plainStates.lockedOut, "Needs your attention"); +}); 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 6/8] 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 bf77bab602a7..e30c2088b006 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 474e44de9548..cf08e93b61bc 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 fe5e1fe14449..3fe237ee4485 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 914d5bcc6baa..7bafe222039a 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 fa84fa5d7ac0..e049342cfb70 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 000000000000..8520901ad56f --- /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"\)/); +}); From 35a34ac34f899998e7066c2b488836026a7bf064 Mon Sep 17 00:00:00 2001 From: ignamiranda <34501347+ignamiranda@users.noreply.github.com> Date: Sun, 23 Aug 2026 05:45:42 -0400 Subject: [PATCH 7/8] Beginner UX: lead batch page with stable outcome header (#11227) Validated on the combined batch board (batch-page static test green) + this branch: i18n suites 16/16 with Vietnamese translations of the batch header/step strings added here. Stable outcome header with the 3-step flow; the collapsible concept card stays as advanced detail. Thank you @ignamiranda! --- src/app/(dashboard)/dashboard/batch/page.tsx | 37 +++++++++- src/i18n/messages/en.json | 7 ++ src/i18n/messages/vi.json | 9 ++- tests/unit/batch-page-static.test.ts | 74 ++++++++++++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 tests/unit/batch-page-static.test.ts diff --git a/src/app/(dashboard)/dashboard/batch/page.tsx b/src/app/(dashboard)/dashboard/batch/page.tsx index 0100b8e94865..258d837e17cf 100644 --- a/src/app/(dashboard)/dashboard/batch/page.tsx +++ b/src/app/(dashboard)/dashboard/batch/page.tsx @@ -246,7 +246,42 @@ export default function BatchPage() { return (
- {/* Concept card (F3) */} + {/* Stable outcome-oriented header (replaces the collapsible card as primary orientation) */} +
+
+

{t("batchConceptTitle")}

+

{t("batchHeaderSubtitle")}

+
+ + {/* Three-step strip */} +
+
+ {t("batchStep1")} + {t("batchStep1Desc")} +
+ chevron_right +
+ {t("batchStep2")} + {t("batchStep2Desc")} +
+ chevron_right +
+ {t("batchStep3")} + {t("batchStep3Desc")} +
+
+ + {/* Primary CTA */} + +
+ + {/* Deeper explanation (optional, collapsible) */} {/* "Batch created" success banner (A-6) — auto-dismiss 5s */} diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 7bafe222039a..a1b934e50bbf 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -805,6 +805,13 @@ "batchConceptBenefit50pct": "50% discount on input + output tokens", "batchConceptAsync24h": "Async with a 24h completion window", "batchConceptUseCases": "Best for bulk classification, evaluations, and embeddings", + "batchHeaderSubtitle": "Run many requests as one job", + "batchStep1": "1 · Upload JSONL", + "batchStep1Desc": "Add requests", + "batchStep2": "2 · Create batch", + "batchStep2Desc": "Run job", + "batchStep3": "3 · Get results", + "batchStep3Desc": "Download output", "filesConceptTitle": "Batch files", "filesConceptSubtitle": "JSONL files used by batches: input requests, results, and errors.", "filesConceptInput": "Input — your JSONL with one request per line", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index e049342cfb70..583036073166 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -974,7 +974,14 @@ "batchFileUsedByCount": "{count, plural, one {# batch} other {# batch}}", "batchFilePreview": "Bản xem trước", "batchFilePreviewTruncated": "Hiển thị {shown} dòng đầu tiên (tổng cộng {total} dòng)", - "batchFileDownloadFull": "Tải xuống toàn bộ tệp" + "batchFileDownloadFull": "Tải xuống toàn bộ tệp", + "batchHeaderSubtitle": "Chạy nhiều yêu cầu như một job duy nhất", + "batchStep1": "1 · Tải lên JSONL", + "batchStep1Desc": "Thêm yêu cầu", + "batchStep2": "2 · Tạo batch", + "batchStep2Desc": "Chạy job", + "batchStep3": "3 · Nhận kết quả", + "batchStep3Desc": "Tải xuống đầu ra" }, "disabled": "Đã tắt", "featureFlagOmnirouteEmergencyFallbackDescription": "Định tuyến các yêu cầu đã hết ngân sách đến nhà cung cấp/mô hình dự phòng khẩn cấp miễn phí.", diff --git a/tests/unit/batch-page-static.test.ts b/tests/unit/batch-page-static.test.ts new file mode 100644 index 000000000000..878b2960504c --- /dev/null +++ b/tests/unit/batch-page-static.test.ts @@ -0,0 +1,74 @@ +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/batch/page.tsx" +); +const enPath = path.join(repoRoot, "src/i18n/messages/en.json"); + +function readBatchPage() { + return fs.readFileSync(pagePath, "utf8"); +} + +function readEnKeys() { + return Object.keys(JSON.parse(fs.readFileSync(enPath, "utf8"))); +} + +test("batch page stable header uses t() for subtitle", () => { + const source = readBatchPage(); + assert.match(source, /batchHeaderSubtitle/); +}); + +test("batch page stable header uses t() for three-step strip", () => { + const source = readBatchPage(); + assert.match(source, /batchStep1/); + assert.match(source, /batchStep2/); + assert.match(source, /batchStep3/); + assert.match(source, /batchStep1Desc/); + assert.match(source, /batchStep2Desc/); + assert.match(source, /batchStep3Desc/); +}); + +test("batch page stable header keeps Create batch CTA using t()", () => { + const source = readBatchPage(); + assert.match(source, /batchListNewButton/); +}); + +test("batch page still renders collapsible BatchConceptCard as optional deeper explanation", () => { + const source = readBatchPage(); + assert.match(source, /BatchConceptCard/); +}); + +test("batch page stable header does not contain hardcoded English", () => { + const source = readBatchPage(); + assert.doesNotMatch(source, /Run many requests as one job/); + assert.doesNotMatch(source, /1 \· Upload JSONL/); + assert.doesNotMatch(source, /2 \· Create batch/); + assert.doesNotMatch(source, /3 \· Get results/); +}); + +test("new i18n keys exist in en.json common namespace", () => { + const enKeys = JSON.parse( + fs.readFileSync( + path.join(repoRoot, "src/i18n/messages/en.json"), + "utf8" + ) + ).common || {}; + const requiredKeys = [ + "batchHeaderSubtitle", + "batchStep1", + "batchStep2", + "batchStep3", + "batchStep1Desc", + "batchStep2Desc", + "batchStep3Desc", + ]; + for (const key of requiredKeys) { + assert.ok(key in enKeys, `Missing i18n key in en.json common namespace: ${key}`); + } +}); \ No newline at end of file From 75ff9bcc5b5d7356011114c2a8b07dbf0da69038 Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Sun, 23 Aug 2026 06:46:27 -0300 Subject: [PATCH 8/8] fix(i18n): Vietnamese translations for the new endpoint keys (#11228) --- src/i18n/messages/vi.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 583036073166..d670c50d20a6 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -4151,7 +4151,10 @@ "notionIntegrationHelp": "Tạo một Tích hợp nội bộ tại", "notionIntegrationToken": "Token tích hợp nội bộ Notion", "notionNotConnected": "Chưa kết nối", - "notionTokenConfigured": "Đã cấu hình token. Các công cụ Notion khả dụng qua MCP." + "notionTokenConfigured": "Đã cấu hình token. Các công cụ Notion khả dụng qua MCP.", + "subtitle": "Dùng endpoint tương thích OpenAI với hầu hết SDK và công cụ.", + "testEndpoint": "Kiểm tra endpoint →", + "advancedProtocols": "Giao thức nâng cao" }, "endpoints": { "tabProxy": "Endpoint Proxy",