From d813bfe1f9391ce58326f19d847e01511ded0da2 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Mon, 24 Aug 2026 00:12:52 -0700 Subject: [PATCH 1/2] CL-6750: Cover Files empty state Upload story in tests --- .../test/library-upload-confirmation.test.tsx | 4 ++- apps/web/test/pages.test.tsx | 27 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/apps/web/test/library-upload-confirmation.test.tsx b/apps/web/test/library-upload-confirmation.test.tsx index bf89a2c17..d8ab16265 100644 --- a/apps/web/test/library-upload-confirmation.test.tsx +++ b/apps/web/test/library-upload-confirmation.test.tsx @@ -38,8 +38,10 @@ function jsonResponse(body: unknown, status = 200): Response { } function uploadFile(container: HTMLDivElement, file: File): void { + // The picker is unlabeled on purpose (CL-6750) — the visible Upload + // button is the only named control; this helper drives the ghost input. const input = container.querySelector( - 'input[aria-label="Upload files"]', + 'input[type="file"]', ) as HTMLInputElement | null; if (input === null) throw new Error("no upload input"); Object.defineProperty(input, "files", { diff --git a/apps/web/test/pages.test.tsx b/apps/web/test/pages.test.tsx index 1ab457630..ed5560512 100644 --- a/apps/web/test/pages.test.tsx +++ b/apps/web/test/pages.test.tsx @@ -24,6 +24,25 @@ describe("empty states", () => { "Upload a file, or let your agents drop their work here", ); }); + + // CL-6750 — empty Files must tell one story: invite to add files. A + // "0 files" count beside the poster reads as a second empty announcement, + // and a labeled ghost file input twins the visible Upload button. + test("empty library is one invitation, not count + poster + twin uploads", () => { + const markup = renderToStaticMarkup( + undefined} />, + ); + expect(markup).toContain("No files yet"); + expect(markup).not.toContain("0 files"); + // One visible Upload in the top bar. + expect(markup).toMatch(/stage-top-bar-actions[\s\S]*?>Upload]*aria-hidden/); + expect(markup).toMatch(/type="file"[^>]*tabindex="-1"/); + }); }); describe("live data", () => { @@ -55,14 +74,18 @@ describe("live data", () => { undefined} />, ); // Hidden file input behind the top-bar Upload action and - // workbench:library:upload. + // workbench:library:upload — unlabeled so it is not a twin control. expect(markup).toContain('type="file"'); - expect(markup).toContain('aria-label="Upload files"'); expect(markup).toContain("sr-only"); + expect(markup).not.toContain('aria-label="Upload files"'); + expect(markup).toMatch(/type="file"[^>]*aria-hidden/); + expect(markup).toMatch(/type="file"[^>]*tabindex="-1"/); // Upload is a top-bar action (mock: primary chip in `.top`). expect(markup).toMatch(/stage-top-bar-actions[\s\S]*?>Upload Date: Mon, 24 Aug 2026 00:12:53 -0700 Subject: [PATCH 2/2] CL-6750: Clean Files empty state to one Upload story --- apps/web/src/pages/library-page.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/web/src/pages/library-page.tsx b/apps/web/src/pages/library-page.tsx index a94333563..9d9674254 100644 --- a/apps/web/src/pages/library-page.tsx +++ b/apps/web/src/pages/library-page.tsx @@ -453,7 +453,11 @@ export function LibraryPage({ } subtitle={ selectedSummary === null - ? `${artifacts.length} files` + ? // Empty Files already has a poster invitation — a "0 files" + // count beside it is a second empty announcement (CL-6750). + artifacts.length === 0 + ? undefined + : `${artifacts.length} files` : artifactKindLabel(selectedSummary.kind) } filter={{ @@ -543,7 +547,8 @@ export function LibraryPage({ type="file" multiple className="sr-only" - aria-label="Upload files" + tabIndex={-1} + aria-hidden="true" onChange={(event) => { const list = event.target.files; if (list !== null && list.length > 0) {