diff --git a/crates/product/ironclaw_webui/frontend/src/design-system/search-field.test.tsx b/crates/product/ironclaw_webui/frontend/src/design-system/search-field.test.tsx index 6fc1f9000e..d7784d79dc 100644 --- a/crates/product/ironclaw_webui/frontend/src/design-system/search-field.test.tsx +++ b/crates/product/ironclaw_webui/frontend/src/design-system/search-field.test.tsx @@ -69,3 +69,18 @@ test("SearchField omits the optional clear action", () => { assert.doesNotMatch(html, / )} diff --git a/crates/product/ironclaw_webui/frontend/src/pages/logs/logs-page.test.ts b/crates/product/ironclaw_webui/frontend/src/pages/logs/logs-page.test.ts index 92e3b0a205..c4f035aa3e 100644 --- a/crates/product/ironclaw_webui/frontend/src/pages/logs/logs-page.test.ts +++ b/crates/product/ironclaw_webui/frontend/src/pages/logs/logs-page.test.ts @@ -47,9 +47,11 @@ function createLogsPageHarness(overrides = {}) { const hookValues = []; let hookCursor = 0; function ConfirmDialog() {} + function SearchField() {} function SelectMenu() {} const context = { ConfirmDialog, + SearchField, SelectMenu, globalThis: {}, React: { @@ -91,6 +93,7 @@ function createLogsPageHarness(overrides = {}) { vm.runInNewContext(logsPageSourceForTest(), context); return { ConfirmDialog, + SearchField, SelectMenu, render() { hookCursor = 0; @@ -355,6 +358,25 @@ test("LogsPage changes the log level through the compact shared SelectMenu", () assert.deepEqual(changes, ["warn"]); }); +test("LogsPage filters targets through the compact shared SearchField", () => { + const changes = []; + const harness = createLogsPageHarness({ + targetFilter: "ironclaw::agent", + setTargetFilter: (value) => changes.push(value), + }); + + const [targetSearch] = componentProps(harness.render(), harness.SearchField); + assert.ok(targetSearch, "expected LogsPage to render the shared SearchField"); + assert.equal(targetSearch.value, "ironclaw::agent"); + assert.equal(targetSearch.size, "sm"); + assert.equal(targetSearch["aria-label"], "logs.filterTarget"); + assert.equal(targetSearch.clearLabel, "settings.clearSearch"); + + targetSearch.onChange("ironclaw::runtime"); + targetSearch.onClear(); + assert.deepEqual(changes, ["ironclaw::runtime", ""]); +}); + test("LogsPage changes the server level through the compact shared SelectMenu", () => { const changes = []; const harness = createLogsPageHarness({ diff --git a/crates/product/ironclaw_webui/frontend/src/pages/logs/logs-page.tsx b/crates/product/ironclaw_webui/frontend/src/pages/logs/logs-page.tsx index c1f8128789..455337b5f0 100644 --- a/crates/product/ironclaw_webui/frontend/src/pages/logs/logs-page.tsx +++ b/crates/product/ironclaw_webui/frontend/src/pages/logs/logs-page.tsx @@ -2,6 +2,7 @@ import { useOutletContext } from "react-router"; import React from "react"; import { ConfirmDialog } from "../../design-system/confirm-dialog"; +import { SearchField } from "../../design-system/search-field"; import { SelectMenu } from "../../design-system/select-menu"; import { useT } from "../../lib/i18n"; import { useLogs } from "./hooks/useLogs"; @@ -192,12 +193,15 @@ export function LogsPage() { /> {/* Target filter */} - setTargetFilter(e.currentTarget.value)} + onChange={setTargetFilter} + onClear={() => setTargetFilter("")} placeholder={t("logs.filterTarget")} - className="h-8 min-w-[10rem] flex-1 rounded-[8px] border border-[var(--v2-panel-border)] bg-[var(--v2-surface-muted)] px-3 text-xs text-[var(--v2-text-base)] placeholder:text-[var(--v2-text-muted)] focus:outline-none focus:ring-1 focus:ring-[var(--v2-accent)]" + aria-label={t("logs.filterTarget")} + clearLabel={t("settings.clearSearch")} + size="sm" + className="min-w-[10rem] flex-1" />
diff --git a/crates/product/ironclaw_webui/frontend/src/pages/workspace/components/workspace-presentation.test.tsx b/crates/product/ironclaw_webui/frontend/src/pages/workspace/components/workspace-presentation.test.tsx index ae238a3a8d..a1e2613325 100644 --- a/crates/product/ironclaw_webui/frontend/src/pages/workspace/components/workspace-presentation.test.tsx +++ b/crates/product/ironclaw_webui/frontend/src/pages/workspace/components/workspace-presentation.test.tsx @@ -19,6 +19,7 @@ vi.mock("../../../lib/i18n", () => ({ "workspace.area.memory": "Speicher", "workspace.fileMeta": "{mime} · {size}", "workspace.filterPlaceholder": "Nach Namen filtern…", + "settings.clearSearch": "Suche löschen", "workspace.pickFileTitle": "Datei aus dem Arbeitsbereich auswählen", "workspace.breadcrumbRoot": "Arbeitsbereich", "workspace.unableOpenDirectory": "Ordner konnte nicht geöffnet werden", @@ -94,13 +95,13 @@ test("workspace tree exposes hierarchy, expansion, selection, and roving focus s assert.equal((html.match(/tabindex="0"/g) || []).length, 1); }); -test("workspace filter and breadcrumb have accessible names and landmarks", () => { +test("workspace uses the shared clearable search field and an accessible breadcrumb", () => { const sidebar = renderToStaticMarkup( {}} @@ -114,6 +115,8 @@ test("workspace filter and breadcrumb have accessible names and landmarks", () = ); assert.match(sidebar, /aria-label="Nach Namen filtern…"/); + assert.match(sidebar, /]*type="search"/); + assert.match(sidebar, /]*aria-label="Suche löschen"/); assert.match(breadcrumb, /