${options.pendingAttachmentReads > 0
- ? html`${t("newSession.readingAttachment")}`
+ ? html`${t("newSession.readingAttachment")}`
: nothing}
diff --git a/ui/src/pages/sessions/view.browser.test.ts b/ui/src/pages/sessions/view.browser.test.ts
index fbfddd733a05..678314a1682d 100644
--- a/ui/src/pages/sessions/view.browser.test.ts
+++ b/ui/src/pages/sessions/view.browser.test.ts
@@ -80,9 +80,7 @@ function sessionsTableHtml() {
: index === 6
? "session-actions-col"
: ""
- }">${
- index === 6 ? `${header}` : header
- }`,
+ }">${index === 6 ? `${header}` : header}`,
)
.join("")}
diff --git a/ui/src/pages/sessions/view.ts b/ui/src/pages/sessions/view.ts
index c117cc44e46e..2327f534ceaf 100644
--- a/ui/src/pages/sessions/view.ts
+++ b/ui/src/pages/sessions/view.ts
@@ -1286,7 +1286,7 @@ function renderSessionsTable(props: SessionsProps, ctx: SessionsTableContext) {
${sortHeader("updated", t("sessionsView.updated"))}
${sortHeader("tokens", t("sessionsView.tokens"))}
|
- ${t("sessionsView.actions")}
+ ${t("sessionsView.actions")}
|
diff --git a/ui/src/pages/workboard/view-card-details.ts b/ui/src/pages/workboard/view-card-details.ts
index 3f978da6c1e3..bc2311c46ef6 100644
--- a/ui/src/pages/workboard/view-card-details.ts
+++ b/ui/src/pages/workboard/view-card-details.ts
@@ -265,7 +265,7 @@ export function renderCardDetailsPanel(props: WorkboardProps) {
${formatPriorityLabel(card.priority)}
- ${t("workboard.detailTitle")}: ${card.title}
+ ${t("workboard.detailTitle")}: ${card.title}
diff --git a/ui/src/styles/about.css b/ui/src/styles/about.css
index 657398f37573..0753199da5a5 100644
--- a/ui/src/styles/about.css
+++ b/ui/src/styles/about.css
@@ -293,16 +293,3 @@
font-size: var(--control-ui-text-xs, 12px);
white-space: nowrap;
}
-
-/* Screen-reader-only live region for copy feedback (no shared utility yet). */
-.about-sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border: 0;
-}
diff --git a/ui/src/styles/base.css b/ui/src/styles/base.css
index b9d5b6402ac7..e6a87a4c1aec 100644
--- a/ui/src/styles/base.css
+++ b/ui/src/styles/base.css
@@ -601,6 +601,19 @@
box-sizing: border-box;
}
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ margin: -1px;
+ padding: 0;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
html,
body {
height: 100%;
diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css
index e8fb480050e4..b7bb6f7c3dc2 100644
--- a/ui/src/styles/chat/layout.css
+++ b/ui/src/styles/chat/layout.css
@@ -2419,18 +2419,6 @@ openclaw-chat-video-player {
min-width: 0;
}
-.agent-chat__sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border: 0;
-}
-
.agent-chat__composer-combobox > :is(textarea, input) {
width: 100%;
min-height: var(--chat-composer-control-height);
diff --git a/ui/src/styles/sessions.css b/ui/src/styles/sessions.css
index 02e277458e1e..93b13da56612 100644
--- a/ui/src/styles/sessions.css
+++ b/ui/src/styles/sessions.css
@@ -410,19 +410,6 @@
min-width: 760px;
}
-.sessions-sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- margin: -1px;
- padding: 0;
- border: 0;
- overflow: hidden;
- clip: rect(0 0 0 0);
- clip-path: inset(50%);
- white-space: nowrap;
-}
-
.sessions-table tbody tr.session-data-row > td {
white-space: nowrap;
}
diff --git a/ui/src/styles/sr-only.browser.test.ts b/ui/src/styles/sr-only.browser.test.ts
new file mode 100644
index 000000000000..d6bd9d464bbf
--- /dev/null
+++ b/ui/src/styles/sr-only.browser.test.ts
@@ -0,0 +1,71 @@
+import { chromium, type Browser } from "playwright";
+import { afterAll, beforeAll, describe, expect, it } from "vitest";
+import { readStyleSheet } from "../../../test/helpers/ui-style-fixtures.js";
+import {
+ canRunPlaywrightChromium,
+ resolvePlaywrightChromiumExecutablePath,
+} from "../test-helpers/control-ui-e2e.ts";
+
+const chromiumExecutablePath = resolvePlaywrightChromiumExecutablePath(chromium.executablePath());
+const describeSrOnly = canRunPlaywrightChromium(chromiumExecutablePath) ? describe : describe.skip;
+
+let browser: Browser;
+
+beforeAll(async () => {
+ if (!canRunPlaywrightChromium(chromiumExecutablePath)) {
+ return;
+ }
+ browser = await chromium.launch({ executablePath: chromiumExecutablePath, headless: true });
+});
+
+afterAll(async () => {
+ await browser?.close().catch(() => {});
+});
+
+describeSrOnly("screen-reader-only utility", () => {
+ it("visually hides content while preserving its accessible status", async () => {
+ const page = await browser.newPage();
+ try {
+ await page.setContent(`
+
+
+ Background task active
+ `);
+
+ const status = page.getByRole("status");
+ await expect
+ .poll(async () => {
+ return await status.evaluate((element) => {
+ const style = getComputedStyle(element);
+ return {
+ borderWidth: style.borderWidth,
+ clip: style.clip,
+ clipPath: style.clipPath,
+ height: style.height,
+ margin: style.margin,
+ overflow: style.overflow,
+ padding: style.padding,
+ position: style.position,
+ whiteSpace: style.whiteSpace,
+ width: style.width,
+ };
+ });
+ })
+ .toEqual({
+ borderWidth: "0px",
+ clip: "rect(0px, 0px, 0px, 0px)",
+ clipPath: "inset(50%)",
+ height: "1px",
+ margin: "-1px",
+ overflow: "hidden",
+ padding: "0px",
+ position: "absolute",
+ whiteSpace: "nowrap",
+ width: "1px",
+ });
+ expect(await status.ariaSnapshot()).toContain("Background task active");
+ } finally {
+ await page.close().catch(() => {});
+ }
+ });
+});
diff --git a/ui/src/styles/workboard.css b/ui/src/styles/workboard.css
index 8124d6341c54..f1a8be897c7b 100644
--- a/ui/src/styles/workboard.css
+++ b/ui/src/styles/workboard.css
@@ -34,19 +34,6 @@
min-height: 0;
}
-.workboard-sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- margin: -1px;
- padding: 0;
- border: 0;
- overflow: hidden;
- clip: rect(0 0 0 0);
- clip-path: inset(50%);
- white-space: nowrap;
-}
-
.workboard-toolbar {
display: flex;
justify-content: space-between;
diff --git a/ui/vitest.config.ts b/ui/vitest.config.ts
index cafab0c5bdca..cbec7052e883 100644
--- a/ui/vitest.config.ts
+++ b/ui/vitest.config.ts
@@ -96,6 +96,7 @@ const nodeDrivenBrowserLayoutTests = [
"src/pages/sessions/view.browser.test.ts",
"src/styles/cursor-policy.browser.test.ts",
"src/styles/chat-file-link-presentation.browser.test.ts",
+ "src/styles/sr-only.browser.test.ts",
] as const;
const mockRegistryUnitTests = [
...uiIsolatedTestFiles.map((testFile) => testFile.slice("ui/".length)),