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] 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 0100b8e948..258d837e17 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 7bafe22203..a1b934e50b 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 e049342cfb..5830360731 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 0000000000..878b296050 --- /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