diff --git a/command-center-sdk/CHANGELOG.md b/command-center-sdk/CHANGELOG.md index 56c844a9..50a29728 100644 --- a/command-center-sdk/CHANGELOG.md +++ b/command-center-sdk/CHANGELOG.md @@ -6,6 +6,8 @@ hosts default to English; locale updates preserve authorization and child state. - Add public `/localization` and `/localization/react` message adapters, English defaults and live localization of resource controls and accessibility messages. +- Preserve selection and open confirmations when the normalized search query has + not changed; a delayed mount-time debounce must not clear them during a locale update. - Keep existing schema IDs, protocol versions and component overrides. External strict validators receive the legacy payload unless their child advertises `locale-context-v1`; locale-aware validators adopt the updated schema. No SDK-owned diff --git a/command-center-sdk/docs/localization.md b/command-center-sdk/docs/localization.md index 41bc7cfb..f1d4d29e 100644 --- a/command-center-sdk/docs/localization.md +++ b/command-center-sdk/docs/localization.md @@ -8,9 +8,9 @@ at `@dev-mainsequence/command-center-sdk/localization/react`. import { CommandCenterLocalizationProvider } from '@dev-mainsequence/command-center-sdk/localization/react'; import type { TranslateSdkMessage } from '@dev-mainsequence/command-center-sdk/localization'; import { useTranslation } from 'react-i18next'; -import { useCallback } from 'react'; +import { useCallback, type ReactNode } from 'react'; -function LocalizedApplication({ children }) { +function LocalizedApplication({ children }: { children: ReactNode }) { const { i18n } = useTranslation('sdk'); const locale = i18n.resolvedLanguage ?? i18n.language; const translate = useCallback((key, values) => diff --git a/command-center-sdk/src/views/README.md b/command-center-sdk/src/views/README.md index 9281caf0..31c6fa52 100644 --- a/command-center-sdk/src/views/README.md +++ b/command-center-sdk/src/views/README.md @@ -144,3 +144,8 @@ title; the host sizes it, and a lead that renders nothing takes no space. No vie Shared message defaults now come from `/localization/react`; wrap views in `CommandCenterLocalizationProvider` to inject application catalogs. Explicit label props retain precedence. See `docs/localization.md` for interpolation/plurals. + +`ResourceListPage` resets selection only when the normalized debounced search +query changes. Mounting, locale updates, and whitespace-only edits to the same +query preserve selection and any open confirmation. Keep translation callbacks +out of transport/query dependencies. diff --git a/command-center-sdk/src/views/ResourceListPage.tsx b/command-center-sdk/src/views/ResourceListPage.tsx index 2d44622d..77735af9 100644 --- a/command-center-sdk/src/views/ResourceListPage.tsx +++ b/command-center-sdk/src/views/ResourceListPage.tsx @@ -394,8 +394,11 @@ export function ResourceListPage({ ]); useEffect(() => { + const nextSearch = searchInput.trim(); + // Mounting, a locale update, or whitespace around the same query must not clear selection. + if (nextSearch === search) return undefined; const timeout = window.setTimeout(() => { - setSearch(searchInput.trim()); + setSearch(nextSearch); setPageIndex(0); setAllMatching(false); selection.clearSelection(); @@ -403,7 +406,7 @@ export function ResourceListPage({ return () => window.clearTimeout(timeout); // Selection callbacks intentionally stay outside dependencies: the query value is the reset boundary. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchInput]); + }, [searchInput, search]); useEffect(() => { setPageIndex(0); diff --git a/command-center-sdk/tests/browser/localization-controls.pw.ts b/command-center-sdk/tests/browser/localization-controls.pw.ts index d29344b3..e9353b09 100644 --- a/command-center-sdk/tests/browser/localization-controls.pw.ts +++ b/command-center-sdk/tests/browser/localization-controls.pw.ts @@ -16,6 +16,9 @@ test.beforeAll(async () => { test("localizes resource selection, long pagination labels and an open confirmation in place", async ({ page }) => { await page.setContent(`
`); + await page.clock.install(); + const pageErrors: string[] = []; + page.on("pageerror", error => pageErrors.push(error.message)); await page.addScriptTag({ content: bundle }); await expect(page.getByRole("button", { name: "Weiter zur nächsten Ergebnisseite" })).toBeVisible(); const pagination = page.getByRole("navigation", { name: "records pagination" }); @@ -34,6 +37,9 @@ test("localizes resource selection, long pagination labels and an open confirmat await dialog.getByLabel("Bestätigungswort").fill("ARC"); // An application-owned update can arrive while focus is inside a modal. await page.getByRole("button", { name: "Change language" }).evaluate((button: HTMLButtonElement) => button.click()); + // A delayed mount-time search reset used to clear selection and destroy the open dialog. + await page.clock.fastForward(1_000); + expect(pageErrors).toEqual([]); await expect(dialog.getByRole("button", { name: "Cancel", exact: true })).toBeVisible(); await expect(confirmationInput).toHaveAttribute("aria-label", "Confirmation word"); await expect(confirmationInput).toHaveValue("ARC");