Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package agama-web-ui for openSUSE:Factory 
checked in at 2026-07-17 18:47:25
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/agama-web-ui (Old)
 and      /work/SRC/openSUSE:Factory/.agama-web-ui.new.24530 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "agama-web-ui"

Fri Jul 17 18:47:25 2026 rev:48 rq:1366195 version:0

Changes:
--------
--- /work/SRC/openSUSE:Factory/agama-web-ui/agama-web-ui.changes        
2026-07-15 16:30:00.194746833 +0200
+++ /work/SRC/openSUSE:Factory/.agama-web-ui.new.24530/agama-web-ui.changes     
2026-07-17 18:47:49.729911606 +0200
@@ -1,0 +2,21 @@
+Thu Jul 16  07:06:02 UTC 2026 - David Diaz <[email protected]>
+
+- Prevent the partition and logical volume forms from flickering
+  when changing the mount point or file system
+  (gh#agama-project/agama#3728, bsc#1271524).
+
+-------------------------------------------------------------------
+Tue Jul 14 16:59:31 UTC 2026 - David Diaz <[email protected]>
+
+- Require translated text in user-facing component props, ensuring
+  users get the interface in their language
+  (gh#agama-project/agama#3696, bsc#1271404).
+
+-------------------------------------------------------------------
+Tue Jul 14 12:38:47 UTC 2026 - David Diaz <[email protected]>
+
+- Align paths and navigation with the Installation breadcrumb
+  instead of the internal overview concept
+  (gh#agama-project/agama#3722).
+
+-------------------------------------------------------------------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ agama.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/package/agama-web-ui.changes 
new/agama/package/agama-web-ui.changes
--- old/agama/package/agama-web-ui.changes      2026-07-14 10:45:10.000000000 
+0200
+++ new/agama/package/agama-web-ui.changes      2026-07-16 14:18:35.000000000 
+0200
@@ -1,4 +1,25 @@
 -------------------------------------------------------------------
+Thu Jul 16  07:06:02 UTC 2026 - David Diaz <[email protected]>
+
+- Prevent the partition and logical volume forms from flickering
+  when changing the mount point or file system
+  (gh#agama-project/agama#3728, bsc#1271524).
+
+-------------------------------------------------------------------
+Tue Jul 14 16:59:31 UTC 2026 - David Diaz <[email protected]>
+
+- Require translated text in user-facing component props, ensuring
+  users get the interface in their language
+  (gh#agama-project/agama#3696, bsc#1271404).
+
+-------------------------------------------------------------------
+Tue Jul 14 12:38:47 UTC 2026 - David Diaz <[email protected]>
+
+- Align paths and navigation with the Installation breadcrumb
+  instead of the internal overview concept
+  (gh#agama-project/agama#3722).
+
+-------------------------------------------------------------------
 Mon Jul 13 14:14:41 UTC 2026 - Ancor Gonzalez Sosa <[email protected]>
 
 - Fixed check for BIOS RAIDs (bsc#1271120).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/InstallerL10nOptions.tsx 
new/agama/src/components/core/InstallerL10nOptions.tsx
--- old/agama/src/components/core/InstallerL10nOptions.tsx      2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/core/InstallerL10nOptions.tsx      2026-07-16 
14:18:35.000000000 +0200
@@ -51,6 +51,8 @@
 import { useInstallerL10n } from "~/context/installerL10n";
 import { localConnection } from "~/utils";
 import { _ } from "~/i18n";
+
+import type { TranslatedString } from "~/i18n";
 import supportedLanguages from "~/languages.json";
 import { ROOT, L10N } from "~/routes/paths";
 import { useProductInfo } from "~/hooks/model/config/product";
@@ -266,7 +268,7 @@
 
 type TextWithLinkToL10nProps = {
   /** The text containing a bracketed substring for the link. */
-  text: string;
+  text: TranslatedString;
   /**
    * Optional handler triggered when the user activates the link. Useful for
    * performing side effects, such as closing the dialog. Navigation may not 
occur
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/ResizeHandle.tsx 
new/agama/src/components/core/ResizeHandle.tsx
--- old/agama/src/components/core/ResizeHandle.tsx      2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/core/ResizeHandle.tsx      2026-07-16 
14:18:35.000000000 +0200
@@ -20,6 +20,8 @@
 import React, { useCallback, useRef } from "react";
 import Icon from "~/components/layout/Icon";
 
+import type { TranslatedString } from "~/i18n";
+
 /** Pixels each arrow-key press adds to or removes from the panel height. */
 const KEYBOARD_STEP = 32;
 
@@ -35,7 +37,7 @@
    */
   onStep: (delta: number) => void;
   /** Accessible label describing what the handle resizes. */
-  label: string;
+  label: TranslatedString;
 };
 
 /**
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/SelectableDataTable.tsx 
new/agama/src/components/core/SelectableDataTable.tsx
--- old/agama/src/components/core/SelectableDataTable.tsx       2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/core/SelectableDataTable.tsx       2026-07-16 
14:18:35.000000000 +0200
@@ -207,7 +207,7 @@
    * Used as the `aria-label` for the row action menu's trigger to improve
    * accessibility.
    */
-  itemActionsLabel?: (d: T) => string | string;
+  itemActionsLabel?: ((d: T) => TranslatedString) | TranslatedString;
 
   /**
    * Custom component to use for rendering the actions cell. When not provided,
@@ -216,7 +216,7 @@
    * The component will receive the actions for the row and the resolved
    * accessible label from `itemActionsLabel`.
    */
-  itemActionsComponent?: React.ComponentType<{ items: IAction[]; label: string 
}>;
+  itemActionsComponent?: React.ComponentType<{ items: IAction[]; label: 
TranslatedString }>;
 
   /**
    * Array of currently selected items.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/SimpleDropdown.test.tsx 
new/agama/src/components/core/SimpleDropdown.test.tsx
--- old/agama/src/components/core/SimpleDropdown.test.tsx       2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/core/SimpleDropdown.test.tsx       2026-07-16 
14:18:35.000000000 +0200
@@ -23,6 +23,7 @@
 import React from "react";
 import { screen } from "@testing-library/react";
 import { installerRender } from "~/test-utils";
+import { _ } from "~/i18n";
 import SimpleDropdown from "./SimpleDropdown";
 
 const mockItems = [
@@ -37,18 +38,18 @@
   });
 
   it("renders the toggle button with the given label", () => {
-    installerRender(<SimpleDropdown items={mockItems} label="Actions for 
0.0.0160" />);
+    installerRender(<SimpleDropdown items={mockItems} label={_("Actions for 
0.0.0160")} />);
     screen.getByRole("button", { name: "Actions for 0.0.0160" });
   });
 
   it("does not show the menu items initially", () => {
-    installerRender(<SimpleDropdown items={mockItems} label="Actions for 
0.0.0160" />);
+    installerRender(<SimpleDropdown items={mockItems} label={_("Actions for 
0.0.0160")} />);
     expect(screen.queryByRole("menuitem", { name: "Activate" })).toBeNull();
   });
 
   it("shows the menu items when the toggle is clicked", async () => {
     const { user } = installerRender(
-      <SimpleDropdown items={mockItems} label="Actions for 0.0.0160" />,
+      <SimpleDropdown items={mockItems} label={_("Actions for 0.0.0160")} />,
     );
 
     const toggle = screen.getByRole("button", { name: "Actions for 0.0.0160" 
});
@@ -61,7 +62,7 @@
 
   it("shows the group label when the menu is open", async () => {
     const { user } = installerRender(
-      <SimpleDropdown items={mockItems} label="Actions for 0.0.0160" />,
+      <SimpleDropdown items={mockItems} label={_("Actions for 0.0.0160")} />,
     );
     await user.click(screen.getByRole("button", { name: "Actions for 0.0.0160" 
}));
     screen.getByText("Actions for 0.0.0160");
@@ -69,7 +70,7 @@
 
   it("calls onClick when a menu item is clicked", async () => {
     const { user } = installerRender(
-      <SimpleDropdown items={mockItems} label="Actions for 0.0.0160" />,
+      <SimpleDropdown items={mockItems} label={_("Actions for 0.0.0160")} />,
     );
     await user.click(screen.getByRole("button", { name: "Actions for 0.0.0160" 
}));
     await user.click(screen.getByRole("menuitem", { name: "Activate" }));
@@ -78,7 +79,7 @@
 
   it("closes the menu after clicking an item", async () => {
     const { user } = installerRender(
-      <SimpleDropdown items={mockItems} label="Actions for 0.0.0160" />,
+      <SimpleDropdown items={mockItems} label={_("Actions for 0.0.0160")} />,
     );
     await user.click(screen.getByRole("button", { name: "Actions for 0.0.0160" 
}));
     await user.click(screen.getByRole("menuitem", { name: "Activate" }));
@@ -87,7 +88,7 @@
 
   it("renders danger items", async () => {
     const { user } = installerRender(
-      <SimpleDropdown items={mockItems} label="Actions for 0.0.0160" />,
+      <SimpleDropdown items={mockItems} label={_("Actions for 0.0.0160")} />,
     );
     await user.click(screen.getByRole("button", { name: "Actions for 0.0.0160" 
}));
     screen.getByRole("menuitem", { name: "Format" });
@@ -98,7 +99,7 @@
       const { user } = installerRender(
         <SimpleDropdown
           items={mockItems}
-          label="Actions for 0.0.0160"
+          label={_("Actions for 0.0.0160")}
           popperProps={{ position: "left" }}
         />,
       );
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/SimpleDropdown.tsx 
new/agama/src/components/core/SimpleDropdown.tsx
--- old/agama/src/components/core/SimpleDropdown.tsx    2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/core/SimpleDropdown.tsx    2026-07-16 
14:18:35.000000000 +0200
@@ -31,6 +31,8 @@
 } from "@patternfly/react-core";
 import Icon from "~/components/layout/Icon";
 
+import type { TranslatedString } from "~/i18n";
+
 /**
  * Represents a single action item in a {@link SimpleDropdown} menu.
  */
@@ -65,7 +67,7 @@
    * providing visual and accessible context about which row or entity
    * the actions belong to.
    */
-  label: string;
+  label: TranslatedString;
   /**
    * Props to pass to the PF Dropdown popper for controlling positioning.
    * Defaults to `{ position: "right" }` to align the menu to the right
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/form/CheckboxField.test.tsx 
new/agama/src/components/form/CheckboxField.test.tsx
--- old/agama/src/components/form/CheckboxField.test.tsx        2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/form/CheckboxField.test.tsx        2026-07-16 
14:18:35.000000000 +0200
@@ -24,6 +24,7 @@
 import { screen } from "@testing-library/react";
 import { installerRender } from "~/test-utils";
 import { useAppForm } from "~/hooks/form";
+import { _ } from "~/i18n";
 
 function CheckboxFieldForm({ initChecked = false }: { initChecked?: boolean }) 
{
   const form = useAppForm({ defaultValues: { licenseAccepted: initChecked } });
@@ -31,7 +32,7 @@
   return (
     <form.AppForm>
       <form.AppField name="licenseAccepted">
-        {(field) => <field.CheckboxField label="Accept license" />}
+        {(field) => <field.CheckboxField label={_("Accept license")} />}
       </form.AppField>
     </form.AppForm>
   );
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/form/CheckboxField.tsx 
new/agama/src/components/form/CheckboxField.tsx
--- old/agama/src/components/form/CheckboxField.tsx     2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/form/CheckboxField.tsx     2026-07-16 
14:18:35.000000000 +0200
@@ -24,14 +24,16 @@
 import { Checkbox } from "@patternfly/react-core";
 import { useFieldContext } from "~/hooks/form-contexts";
 
+import type { TranslatedString } from "~/i18n";
+
 type CheckboxFieldProps = {
-  label: string;
+  label: TranslatedString;
   description?: React.ReactNode;
   /**
    * Literal accessible name for the checkbox. Replaces the visible label as 
the
    * accessible name; use only when the visible label is not descriptive 
enough.
    */
-  "aria-label"?: string;
+  "aria-label"?: TranslatedString;
   /**
    * IDs of elements that name the checkbox. Replaces the visible label as the
    * accessible name; use when other on-screen elements describe it.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/form/Fieldset.test.tsx 
new/agama/src/components/form/Fieldset.test.tsx
--- old/agama/src/components/form/Fieldset.test.tsx     2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/form/Fieldset.test.tsx     2026-07-16 
14:18:35.000000000 +0200
@@ -23,12 +23,13 @@
 import React from "react";
 import { screen } from "@testing-library/react";
 import { installerRender } from "~/test-utils";
+import { _ } from "~/i18n";
 import { Fieldset } from "./Fieldset";
 
 describe("Fieldset", () => {
   it("renders the legend", () => {
     installerRender(
-      <Fieldset legend="Network Settings">
+      <Fieldset legend={_("Network Settings")}>
         <input type="text" />
       </Fieldset>,
     );
@@ -37,7 +38,7 @@
 
   it("renders children", () => {
     installerRender(
-      <Fieldset legend="Settings">
+      <Fieldset legend={_("Settings")}>
         <input aria-label="Test input" />
       </Fieldset>,
     );
@@ -46,7 +47,7 @@
 
   it("renders description when provided", () => {
     installerRender(
-      <Fieldset legend="Advanced" description="Configure advanced options">
+      <Fieldset legend={_("Advanced")} description="Configure advanced 
options">
         <input type="text" />
       </Fieldset>,
     );
@@ -55,7 +56,7 @@
 
   it("passes through native fieldset attributes", () => {
     installerRender(
-      <Fieldset legend="Disabled Fieldset" disabled 
data-testid="test-fieldset">
+      <Fieldset legend={_("Disabled Fieldset")} disabled 
data-testid="test-fieldset">
         <input aria-label="Disabled input" />
       </Fieldset>,
     );
@@ -65,7 +66,7 @@
 
   it("supports className attribute", () => {
     installerRender(
-      <Fieldset legend="Styled" className="custom-class" 
data-testid="styled-fieldset">
+      <Fieldset legend={_("Styled")} className="custom-class" 
data-testid="styled-fieldset">
         <input type="text" />
       </Fieldset>,
     );
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/form/Fieldset.tsx 
new/agama/src/components/form/Fieldset.tsx
--- old/agama/src/components/form/Fieldset.tsx  2026-07-14 10:45:10.000000000 
+0200
+++ new/agama/src/components/form/Fieldset.tsx  2026-07-16 14:18:35.000000000 
+0200
@@ -24,11 +24,13 @@
 import NestedContent from "~/components/core/NestedContent";
 import Text from "~/components/core/Text";
 
+import type { TranslatedString } from "~/i18n";
+
 export interface FieldsetProps extends 
React.FieldsetHTMLAttributes<HTMLFieldSetElement> {
   /**
    * Text displayed as the fieldset legend
    */
-  legend: string;
+  legend: TranslatedString;
   /**
    * Optional ID for the legend element.
    *
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/form/conventions.md 
new/agama/src/components/form/conventions.md
--- old/agama/src/components/form/conventions.md        2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/form/conventions.md        2026-07-16 
14:18:35.000000000 +0200
@@ -41,6 +41,10 @@
   - [Why shake Lives in useUpdateConfig, Not the 
Caller](#why-shake-lives-in-useupdateconfig-not-the-caller)
   - [Putting It Together](#putting-it-together)
   - [Trade-offs and When Not to Use This 
Pattern](#trade-offs-and-when-not-to-use-this-pattern)
+- [Derived Suspense Data Without 
Flicker](#derived-suspense-data-without-flicker)
+  - [The Problem](#the-problem-1)
+  - [The Solution: useDeferredValue + a Local Suspense 
Boundary](#the-solution-usedeferredvalue--a-local-suspense-boundary)
+  - [When to Use This Pattern](#when-to-use-this-pattern)
 - [Code Organization](#code-organization)
   - [Directory Structure](#directory-structure)
   - [File Naming](#file-naming)
@@ -1339,6 +1343,107 @@
 
 ---
 
+## Derived Suspense Data Without Flicker
+
+`withFrozenQuery` protects a form from refetches of its **initial** data. A
+different problem appears when a field derives read-only data from a
+`useSuspenseQuery` that is **re-keyed by what the user types**: size hints
+solved by the backend, availability checks, previews, etc.
+
+The running example is the size hint in `shared/SizeFields.tsx`, which solves
+min/max sizes for the chosen mount point and filesystem.
+
+### The Problem
+
+The derived query key depends on a field value:
+
+```tsx
+// key changes every time the mount point or filesystem changes
+useSuspenseQuery({ queryKey: ["solvedStorageModel", JSON.stringify(config)], 
... });
+```
+
+When the value changes, the key changes, and there is no cached data for the
+new key. `useSuspenseQuery` suspends. If the nearest `<Suspense>` boundary is
+the page shell (`Page.Content`), React swaps the **entire form** for the
+loading fallback and back on every change: the form visibly blinks.
+
+> Note: `placeholderData: keepPreviousData` (TanStack Query v5's replacement
+> for the removed `keepPreviousData: true` option, see the
+> [v5 migration 
guide](https://tanstack.com/query/latest/docs/framework/react/guides/migrating-to-v5#removed-keeppreviousdata-in-favor-of-placeholderdata-identity-function))
+> does not solve this: `useSuspenseQuery` ignores the `placeholderData` option
+> (verified against v5.101.2, which hard-codes it to `undefined`) and suspends 
on every key change anyway. Honoring
+> it would mean switching to plain `useQuery` with manual loading handling; the
+> pattern below keeps suspense instead.
+
+### The Solution: useDeferredValue + a Local Suspense Boundary
+
+Two pieces, each solving one half of the problem:
+
+1. **`useDeferredValue`** on the inputs that drive the query. The urgent render
+   uses the previous (cached) value, so it never suspends. React then 
re-renders
+   in the background with the new value; if that suspends, React keeps the
+   already-committed content on screen instead of the fallback. This makes 
every
+   change **after the first** show stale data until the fresh data is ready: no
+   fallback, no blink.
+
+2. **A local `<Suspense>` boundary** wrapping only the suspending content. The
+   first time the content mounts there is nothing to defer to, so it does
+   suspend once. The local boundary contains that first suspension to the small
+   derived area instead of letting it reach the page shell.
+
+Isolate the suspending work into its own component so the boundary sits 
directly
+above it (a boundary cannot catch a component suspending in its own body), and
+so that sibling fields that do not need the derived data never suspend:
+
+```tsx
+// Only this component consults the solver, and it defers its inputs.
+function AutomaticSizeNote({ committedMountPoint, filesystem, useSolvedSizes 
}) {
+  const deferredMountPoint = useDeferredValue(committedMountPoint);
+  const deferredFilesystem = useDeferredValue(filesystem);
+  const volume = useVolumeTemplate(deferredMountPoint);
+  const solvedSizes = useSolvedSizes(deferredMountPoint, deferredFilesystem);
+  // ...render the note from volume + solvedSizes
+}
+
+// The boundary lives right above the suspending component. Fallback is null:
+// only the first solve reaches it, and the derived content has no fixed shape
+// to placeholder without misrepresenting it.
+case SIZE_MODE.AUTO:
+  return (
+    <Suspense fallback={null}>
+      <AutomaticSizeNote
+        committedMountPoint={committedMountPoint}
+        filesystem={filesystem}
+        useSolvedSizes={useSolvedSizes}
+      />
+    </Suspense>
+  );
+```
+
+Choose the fallback deliberately. Because `useDeferredValue` keeps previous
+content on screen for every change after the first, the fallback is only ever
+seen once (initial mount). Prefer `null` when the derived content has no fixed
+shape; use a skeleton only when the final content has a single, stable layout.
+
+### When to Use This Pattern
+
+- Read-only data derived from a `useSuspenseQuery` whose key changes as the 
user
+  edits a field, **and** the nearest Suspense boundary wraps enough UI that
+  re-suspending is visually disruptive.
+
+Do not reach for it when:
+
+- The derived query key is stable (it will not re-suspend after the first 
load).
+- The data is editable form state, not read-only derived data (use TanStack 
Form
+  listeners and field synchronization instead).
+
+**References:**
+
+- React: [`useDeferredValue` — showing stale content while fresh content 
loads](https://react.dev/reference/react/useDeferredValue#showing-stale-content-while-fresh-content-is-loading)
+- React: [`<Suspense>`](https://react.dev/reference/react/Suspense)
+
+---
+
 ## Code Organization
 
 The following conventions apply to all forms using TanStack Form across the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/form/validation-helpers.ts 
new/agama/src/components/form/validation-helpers.ts
--- old/agama/src/components/form/validation-helpers.ts 2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/form/validation-helpers.ts 2026-07-16 
14:18:35.000000000 +0200
@@ -38,6 +38,8 @@
  *
  */
 
+import type { TranslatedString } from "~/i18n";
+
 /** Types */
 
 /**
@@ -111,7 +113,10 @@
  * requiredString("  ", "Name is required") // "Name is required"
  * requiredString("foo", "Name is required") // undefined
  */
-export const requiredString = (value: string, message: string): string | 
undefined => {
+export const requiredString = (
+  value: string,
+  message: TranslatedString,
+): TranslatedString | undefined => {
   return value.trim().length === 0 ? message : undefined;
 };
 
@@ -129,8 +134,8 @@
   value: number | undefined,
   min: number,
   max: number,
-  message: string,
-): string | undefined => {
+  message: TranslatedString,
+): TranslatedString | undefined => {
   if (value === undefined) return undefined;
   return value >= min && value <= max ? undefined : message;
 };
@@ -147,9 +152,9 @@
   value: number | undefined,
   min: number,
   max: number,
-  emptyMessage: string,
-  invalidMessage: string,
-): string | undefined => {
+  emptyMessage: TranslatedString,
+  invalidMessage: TranslatedString,
+): TranslatedString | undefined => {
   if (value === undefined || value === null) return emptyMessage;
   return intRange(value, min, max, invalidMessage);
 };
@@ -166,8 +171,8 @@
   value: number | undefined,
   min: number,
   max: number,
-  message: string,
-): string | undefined => {
+  message: TranslatedString,
+): TranslatedString | undefined => {
   return intRange(value, min, max, message);
 };
 
@@ -185,9 +190,9 @@
 export const requiredValidList = (
   value: string[],
   predicate: (item: string) => boolean,
-  emptyMessage: string,
-  invalidMessage: string,
-): string | undefined => {
+  emptyMessage: TranslatedString,
+  invalidMessage: TranslatedString,
+): TranslatedString | undefined => {
   if (value.length === 0) return emptyMessage;
   return value.every(predicate) ? undefined : invalidMessage;
 };
@@ -203,8 +208,8 @@
 export const optionalValidList = (
   value: string[],
   predicate: (item: string) => boolean,
-  invalidMessage: string,
-): string | undefined => {
+  invalidMessage: TranslatedString,
+): TranslatedString | undefined => {
   return value.every(predicate) ? undefined : invalidMessage;
 };
 
@@ -219,9 +224,9 @@
 export const requiredValidString = (
   value: string,
   predicate: (s: string) => boolean,
-  emptyMessage: string,
-  invalidMessage: string,
-): string | undefined => {
+  emptyMessage: TranslatedString,
+  invalidMessage: TranslatedString,
+): TranslatedString | undefined => {
   if (value.length === 0) return emptyMessage;
   return predicate(value) ? undefined : invalidMessage;
 };
@@ -237,8 +242,8 @@
 export const optionalValidString = (
   value: string,
   predicate: (s: string) => boolean,
-  invalidMessage: string,
-): string | undefined => {
+  invalidMessage: TranslatedString,
+): TranslatedString | undefined => {
   if (value === "") return undefined;
   return predicate(value) ? undefined : invalidMessage;
 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/layout/Header.tsx 
new/agama/src/components/layout/Header.tsx
--- old/agama/src/components/layout/Header.tsx  2026-07-14 10:45:10.000000000 
+0200
+++ new/agama/src/components/layout/Header.tsx  2026-07-16 14:18:35.000000000 
+0200
@@ -113,7 +113,7 @@
   if (product && breadcrumbs && !hideSummaryLink) {
     items.push({
       isEditorial: true,
-      path: ROOT.overview,
+      path: ROOT.root,
       // TRANSLATORS: First breadcrumb item, linking back to the main page
       // where the whole installation can be reviewed.
       label: _("Installation"),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/layout/Loading.test.tsx 
new/agama/src/components/layout/Loading.test.tsx
--- old/agama/src/components/layout/Loading.test.tsx    2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/layout/Loading.test.tsx    2026-07-16 
14:18:35.000000000 +0200
@@ -33,7 +33,7 @@
   });
 
   it("uses provided aria-label", async () => {
-    plainRender(<Loading aria-label="Loading something" />);
+    plainRender(<Loading aria-label={_("Loading something")} />);
     const icon = await screen.findByLabelText("Loading something");
     expect(icon).toHaveRole("progressbar");
   });
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/layout/Loading.tsx 
new/agama/src/components/layout/Loading.tsx
--- old/agama/src/components/layout/Loading.tsx 2026-07-14 10:45:10.000000000 
+0200
+++ new/agama/src/components/layout/Loading.tsx 2026-07-16 14:18:35.000000000 
+0200
@@ -31,7 +31,7 @@
   /** Text to be rendered alongside the spinner */
   text?: TranslatedString;
   /** Accessible text, required when rendering only the spinner */
-  "aria-label"?: string;
+  "aria-label"?: TranslatedString;
   /** Loader style, full screen or inline */
   variant?: "full-screen" | "inline";
   /** Size for the spinner icon */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/network/ConnectionsTable.tsx 
new/agama/src/components/network/ConnectionsTable.tsx
--- old/agama/src/components/network/ConnectionsTable.tsx       2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/network/ConnectionsTable.tsx       2026-07-16 
14:18:35.000000000 +0200
@@ -383,7 +383,15 @@
             },
           ].filter(Boolean);
         }}
-        itemActionsLabel={(c: Connection) => `Actions for ${c.id}`}
+        itemActionsLabel={(c: Connection) =>
+          sprintf(
+            // TRANSLATORS: accessible label for the connection actions toggle
+            // button. %s is replaced by connection id, e.g. "Actions for
+            // Ethernet 2"
+            _("Actions for %s"),
+            c.id,
+          )
+        }
         emptyState={
           <EmptyState
             headingLevel="h2"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/connection-form/validations.ts 
new/agama/src/components/network/connection-form/validations.ts
--- old/agama/src/components/network/connection-form/validations.ts     
2026-07-14 10:45:10.000000000 +0200
+++ new/agama/src/components/network/connection-form/validations.ts     
2026-07-16 14:18:35.000000000 +0200
@@ -54,6 +54,8 @@
 import { ADDRESS_REQUIRED_MODES, BridgeStpMode, FormIpMode } from "./fields";
 import { _, formatList } from "~/i18n";
 
+import type { TranslatedString } from "~/i18n";
+
 import type {
   ValidationResult,
   FieldsValidationResult,
@@ -87,8 +89,8 @@
   mode: FormIpModeType,
   addresses: string[],
   isValid: (s: string) => boolean,
-  emptyMessage: string,
-  invalidMessage: string,
+  emptyMessage: TranslatedString,
+  invalidMessage: TranslatedString,
 ) =>
   ADDRESS_REQUIRED_MODES.includes(mode)
     ? requiredValidList(addresses, isValid, emptyMessage, invalidMessage)
@@ -105,8 +107,8 @@
   mode: FormIpModeType,
   gateway: string,
   isValid: (s: string) => boolean,
-  emptyMessage: string,
-  invalidMessage: string,
+  emptyMessage: TranslatedString,
+  invalidMessage: TranslatedString,
 ) => {
   if (mode === FormIpMode.MANUAL)
     return requiredValidString(gateway, isValid, emptyMessage, invalidMessage);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/overview/SystemInformationSection.tsx 
new/agama/src/components/overview/SystemInformationSection.tsx
--- old/agama/src/components/overview/SystemInformationSection.tsx      
2026-07-14 10:45:10.000000000 +0200
+++ new/agama/src/components/overview/SystemInformationSection.tsx      
2026-07-16 14:18:35.000000000 +0200
@@ -39,9 +39,11 @@
 
 import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
 
+import type { TranslatedString } from "~/i18n";
+
 type ItemProps = {
   /** The label/term for this field */
-  label: string;
+  label: TranslatedString;
   /** The value/description for this field */
   children: React.ReactNode;
 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/questions/RegistrationCertificateQuestion.tsx 
new/agama/src/components/questions/RegistrationCertificateQuestion.tsx
--- old/agama/src/components/questions/RegistrationCertificateQuestion.tsx      
2026-07-14 10:45:10.000000000 +0200
+++ new/agama/src/components/questions/RegistrationCertificateQuestion.tsx      
2026-07-16 14:18:35.000000000 +0200
@@ -34,10 +34,12 @@
 import { Popup } from "~/components/core";
 import QuestionActions from "~/components/questions/QuestionActions";
 import { _ } from "~/i18n";
+
+import type { TranslatedString } from "~/i18n";
 import type { AnswerCallback, Question } from "~/model/question";
 
 type QuestionDataProps = {
-  label: string;
+  label: TranslatedString;
   value?: string;
 };
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/questions/UnsupportedAutoYaST.tsx 
new/agama/src/components/questions/UnsupportedAutoYaST.tsx
--- old/agama/src/components/questions/UnsupportedAutoYaST.tsx  2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/questions/UnsupportedAutoYaST.tsx  2026-07-16 
14:18:35.000000000 +0200
@@ -34,6 +34,8 @@
 import QuestionActions from "~/components/questions/QuestionActions";
 import { sprintf } from "sprintf-js";
 import { _ } from "~/i18n";
+
+import type { TranslatedString } from "~/i18n";
 import type { AnswerCallback, Question } from "~/model/question";
 
 const UnsupportedElements = ({
@@ -42,8 +44,8 @@
   description,
 }: {
   elements: string[];
-  title: string;
-  description: string;
+  title: TranslatedString;
+  description: TranslatedString;
 }) => {
   if (elements.length === 0) {
     return undefined;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/software/SoftwarePage.tsx 
new/agama/src/components/software/SoftwarePage.tsx
--- old/agama/src/components/software/SoftwarePage.tsx  2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/software/SoftwarePage.tsx  2026-07-16 
14:18:35.000000000 +0200
@@ -53,6 +53,8 @@
 import { isPatternSelected } from "~/utils/software";
 import { SOFTWARE as PATHS } from "~/routes/paths";
 import { _, n_ } from "~/i18n";
+
+import type { TranslatedString } from "~/i18n";
 import { PROPOSAL_QUERY_KEY, EXTENDED_CONFIG_QUERY_KEY } from 
"~/hooks/model/proposal";
 
 import type { Pattern } from "~/model/system/software";
@@ -68,8 +70,8 @@
   buttonText,
 }: {
   to: string;
-  body: string;
-  buttonText: string;
+  body: TranslatedString;
+  buttonText: TranslatedString;
 }) => (
   // TRANSLATORS: empty state title for a software section with nothing 
selected
   <EmptyState headingLevel="h4" titleText={_("None selected")} variant="sm">
@@ -87,7 +89,7 @@
 /**
  * Informational empty state shown when patterns are not available.
  */
-const NoAvailable = ({ title, body }: { title: string; body: string }) => (
+const NoAvailable = ({ title, body }: { title: TranslatedString; body: 
TranslatedString }) => (
   <EmptyState headingLevel="h4" titleText={title} variant="sm">
     <EmptyStateBody>{body}</EmptyStateBody>
   </EmptyState>
@@ -208,11 +210,11 @@
 
 type SoftwareSectionProps = {
   /** Section heading. */
-  title: string;
+  title: TranslatedString;
   /** Optional explanatory text rendered below the heading. */
   description?: React.ReactNode;
   /** Label for the link that opens the pattern selection page. */
-  buttonText: string;
+  buttonText: TranslatedString;
   /** Total number of patterns in this group, selected or not. */
   totalCount: number;
   /** Patterns in this group that are currently selected. */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/storage/dasd/DASDTable.tsx 
new/agama/src/components/storage/dasd/DASDTable.tsx
--- old/agama/src/components/storage/dasd/DASDTable.tsx 2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/storage/dasd/DASDTable.tsx 2026-07-16 
14:18:35.000000000 +0200
@@ -682,7 +682,12 @@
             filterByDevice: true,
           })
         }
-        itemActionsLabel={(d: Device) => `Actions for ${d.channel}`}
+        itemActionsLabel={(d: Device) =>
+          // TRANSLATORS: accessible label for the connection actions toggle
+          // button. %s is replaced by device channel identifier, e.g. "Actions
+          // for 0.0.0150"
+          sprintf(_("Actions for %s"), d.channel)
+        }
         itemActionsComponent={SimpleDropdown}
         emptyState={
           <EmptyState
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/storage/shared/SizeFields.tsx 
new/agama/src/components/storage/shared/SizeFields.tsx
--- old/agama/src/components/storage/shared/SizeFields.tsx      2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/storage/shared/SizeFields.tsx      2026-07-16 
14:18:35.000000000 +0200
@@ -20,7 +20,7 @@
  * find current contact information at www.suse.com.
  */
 
-import React from "react";
+import React, { Suspense, useDeferredValue } from "react";
 import { sprintf } from "sprintf-js";
 import { Flex } from "@patternfly/react-core";
 import Text from "~/components/core/Text";
@@ -269,6 +269,54 @@
 );
 
 /**
+ * Automatic size note: the only content that depends on the backend size
+ * solver.
+ *
+ * Isolated into its own component (rendered inside a Suspense boundary by
+ * {@link SizeFieldsContent}) so that solving never suspends unrelated UI. The
+ * solve is a suspense query keyed by mount point + filesystem, so changing
+ * either creates a new query with no cached data and would normally trigger 
the
+ * nearest Suspense fallback.
+ *
+ * useDeferredValue lets React render first with the previous (cached) values,
+ * so the urgent update does not suspend. The new solve then runs in the
+ * background while React keeps the previously committed note visible instead 
of
+ * showing the Suspense fallback. Only the initial solve (when no cached result
+ * exists) reaches the fallback.
+ */
+function AutomaticSizeNote({
+  committedMountPoint,
+  filesystem,
+  useSolvedSizes,
+}: {
+  committedMountPoint: string;
+  filesystem: string;
+  useSolvedSizes: UseSolvedSizes;
+}) {
+  const deferredMountPoint = useDeferredValue(committedMountPoint);
+  const deferredFilesystem = useDeferredValue(filesystem);
+
+  // Use committedMountPoint (not live mountPoint) to avoid reacting to 
incomplete input.
+  // This prevents showing misleading size hints while user types "/ho..." and 
avoids
+  // expensive useVolumeTemplate recalculations on every keystroke.
+  const volume = useVolumeTemplate(deferredMountPoint);
+
+  // Calculate solved sizes - only recalculates when committedMountPoint or 
filesystem change.
+  // The host form provides the size-solving logic (partition vs logical 
volume).
+  const solvedSizes = useSolvedSizes(deferredMountPoint, deferredFilesystem);
+  const automaticSizeNote = useAutomaticSizeNote(volume, solvedSizes);
+
+  return (
+    <Flex direction={{ default: "column" }} gap={{ default: "gapXs" }}>
+      <Text isBold textStyle="textColorSubtle">
+        {automaticSizeNote.sizeLabel}
+      </Text>
+      <Text component="small">{automaticSizeNote.rationale}</Text>
+    </Flex>
+  );
+}
+
+/**
  * Inner component that renders size mode-specific inputs and info notes.
  */
 const SizeFieldsContent = withForm({
@@ -279,25 +327,21 @@
     sizeMode: SIZE_MODE.AUTO,
   } as SizeFieldsContentProps,
   render: function Render({ form, committedMountPoint, filesystem, sizeMode, 
useSolvedSizes }) {
-    // Use committedMountPoint (not live mountPoint) to avoid reacting to 
incomplete input.
-    // This prevents showing misleading size hints while user types "/ho..." 
and avoids
-    // expensive useVolumeTemplate recalculations on every keystroke.
-    const volume = useVolumeTemplate(committedMountPoint);
-
-    // Calculate solved sizes - only recalculates when committedMountPoint or 
filesystem change.
-    // The host form provides the size-solving logic (partition vs logical 
volume).
-    const solvedSizes = useSolvedSizes(committedMountPoint, filesystem);
-    const automaticSizeNote = useAutomaticSizeNote(volume, solvedSizes);
-
     switch (sizeMode) {
       case SIZE_MODE.AUTO:
+        // Only AUTO consults the backend solver. It renders inside a local
+        // Suspense boundary so the first solve stays contained here (see
+        // AutomaticSizeNote, which defers its inputs via useDeferredValue to 
keep
+        // the previous note visible on later changes). Fallback is null: only 
the
+        // first solve reaches it and the note has no fixed shape to 
placeholder.
         return (
-          <Flex direction={{ default: "column" }} gap={{ default: "gapXs" }}>
-            <Text isBold textStyle="textColorSubtle">
-              {automaticSizeNote.sizeLabel}
-            </Text>
-            <Text component="small">{automaticSizeNote.rationale}</Text>
-          </Flex>
+          <Suspense fallback={null}>
+            <AutomaticSizeNote
+              committedMountPoint={committedMountPoint}
+              filesystem={filesystem}
+              useSolvedSizes={useSolvedSizes}
+            />
+          </Suspense>
         );
 
       case SIZE_MODE.FIXED:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/storage/zfcp/ZFCPDevicesTable.tsx 
new/agama/src/components/storage/zfcp/ZFCPDevicesTable.tsx
--- old/agama/src/components/storage/zfcp/ZFCPDevicesTable.tsx  2026-07-14 
10:45:10.000000000 +0200
+++ new/agama/src/components/storage/zfcp/ZFCPDevicesTable.tsx  2026-07-16 
14:18:35.000000000 +0200
@@ -413,7 +413,12 @@
         itemActions={(device: System.Device) =>
           buildActions(device, deviceConfig(device), addDevices, 
removeDevices, checkLunScan)
         }
-        itemActionsLabel={(d: System.Device) => `Actions for ${d.lun}`}
+        itemActionsLabel={(d: System.Device) =>
+          // TRANSLATORS: accessible label for the connection actions toggle
+          // button. %s is replaced with the zFCP LUN, e.g. "Actions for
+          // 0x4010403200000000"
+          sprintf(_("Actions for %s"), d.lun)
+        }
         emptyState={
           <EmptyState
             headingLevel="h2"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/users/authentication-form/Form.test.tsx 
new/agama/src/components/users/authentication-form/Form.test.tsx
--- old/agama/src/components/users/authentication-form/Form.test.tsx    
2026-07-14 10:45:10.000000000 +0200
+++ new/agama/src/components/users/authentication-form/Form.test.tsx    
2026-07-16 14:18:35.000000000 +0200
@@ -190,7 +190,7 @@
       expect(mockPutConfig).not.toHaveBeenCalled();
       screen.getByText("No changes to apply");
       const link = screen.getByRole("link", { name: "installation" });
-      expect(link).toHaveAttribute("href", "/overview");
+      expect(link).toHaveAttribute("href", "/");
     });
 
     it("creates first user when checkbox is checked", async () => {
@@ -405,7 +405,7 @@
 
       await screen.findByText("Changes successfully applied");
       const link = screen.getByRole("link", { name: "installation" });
-      expect(link).toHaveAttribute("href", "/overview");
+      expect(link).toHaveAttribute("href", "/");
     });
   });
 });
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/hooks/use-form-submit.tsx 
new/agama/src/hooks/use-form-submit.tsx
--- old/agama/src/hooks/use-form-submit.tsx     2026-07-14 10:45:10.000000000 
+0200
+++ new/agama/src/hooks/use-form-submit.tsx     2026-07-16 14:18:35.000000000 
+0200
@@ -325,7 +325,7 @@
                       }
                     >
                       {(linkText) => (
-                        <Link isInline variant="link" to={ROOT.overview}>
+                        <Link isInline variant="link" to={ROOT.root}>
                           {linkText}
                         </Link>
                       )}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/i18n.ts new/agama/src/i18n.ts
--- old/agama/src/i18n.ts       2026-07-14 10:45:10.000000000 +0200
+++ new/agama/src/i18n.ts       2026-07-16 14:18:35.000000000 +0200
@@ -150,11 +150,16 @@
  * Wrapper around Intl.ListFormat to get a language-specific representation of 
the given list of
  * strings.
  *
- * @param {string[]} list iterable list of strings to represent
- * @param {object} options passed to the Intl.ListFormat constructor
- * @return {string} concatenation of the original strings with the correct 
language-specific
+ * Preserves the input type: formatting a list of {@link TranslatedString}
+ * values produces a {@link TranslatedString}, while a list of plain strings
+ * (e.g. device names or mount paths) stays a plain string.
+ *
+ * @param list iterable list of strings to represent
+ * @param options passed to the Intl.ListFormat constructor
+ * @return concatenation of the original strings with the correct 
language-specific
  *  separators according to the currently selected language for the Agama UI
  */
-const formatList = (list, options = {}) => agama.formatList(list, options);
+const formatList = <T extends string | TranslatedString>(list: T[], options: 
object = {}): T =>
+  agama.formatList(list, options) as T;
 
 export { _, n_, N_, Nn_, formatList };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/router.tsx new/agama/src/router.tsx
--- old/agama/src/router.tsx    2026-07-14 10:45:10.000000000 +0200
+++ new/agama/src/router.tsx    2026-07-16 14:18:35.000000000 +0200
@@ -52,6 +52,10 @@
 // bookmarked URLs and external links to naturally transition.
 const legacyRedirects = () => [
   {
+    path: "/overview",
+    element: <Navigate to={PATHS.root} replace />,
+  },
+  {
     path: "/hostname",
     element: <Navigate to={SYSTEM.root} replace />,
   },
@@ -71,10 +75,6 @@
 
 const rootRoutes = () => [
   {
-    path: "/overview",
-    element: <OverviewPage />,
-  },
-  {
     path: SYSTEM.root,
     element: <SystemPage />,
     handle: { name: N_("System"), icon: "server" },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/routes/paths.ts 
new/agama/src/routes/paths.ts
--- old/agama/src/routes/paths.ts       2026-07-14 10:45:10.000000000 +0200
+++ new/agama/src/routes/paths.ts       2026-07-16 14:18:35.000000000 +0200
@@ -49,7 +49,6 @@
 const ROOT = {
   root: "/",
   login: "/login",
-  overview: "/overview",
   installation: "/installation",
   installationProgress: "/installation/progress",
   installationFinished: "/installation/finished",

++++++ agama.obsinfo ++++++
--- /var/tmp/diff_new_pack.qine92/_old  2026-07-17 18:47:57.962189164 +0200
+++ /var/tmp/diff_new_pack.qine92/_new  2026-07-17 18:47:57.978189703 +0200
@@ -1,5 +1,5 @@
 name: agama
-version: 22+398.d23cf880d
-mtime: 1784018710
-commit: d23cf880d8ccae5e227b611b1b1ece43b8dc3d72
+version: 22+456.ce0fb8baf
+mtime: 1784204315
+commit: ce0fb8baf906e6dc037106481ae0640cc743cee2
 

Reply via email to