diff --git a/docs/LOCALIZATION_AND_HELP_QUALITY.md b/docs/LOCALIZATION_AND_HELP_QUALITY.md
index 13d4782..c3c3eca 100644
--- a/docs/LOCALIZATION_AND_HELP_QUALITY.md
+++ b/docs/LOCALIZATION_AND_HELP_QUALITY.md
@@ -103,6 +103,14 @@ modal at narrow widths, closes with Escape, and restores focus to the triggering
control. Module journeys should add their own exact high-risk mappings; they do
not need to reimplement the keyboard or dialog mechanics.
+The same conformance suite mounts the production Forms Runtime self-service and
+assisted Anwohnerparkausweis surfaces with German module translations. Desktop
+and mobile runs traverse native controls by keyboard, inspect accessible names
+and landmarks, run WCAG 2.1 A/AA automation, verify responsive overflow, and
+retain independent per-field assisted provenance. Physical assistive-technology
+spot checks remain release evidence rather than being represented as browser
+automation.
+
## Verification
```bash
diff --git a/docs/UI_UX_DECISION_LEDGER.md b/docs/UI_UX_DECISION_LEDGER.md
index 5e04e61..6eb5d30 100644
--- a/docs/UI_UX_DECISION_LEDGER.md
+++ b/docs/UI_UX_DECISION_LEDGER.md
@@ -236,7 +236,9 @@ instead of reproducing their behavior.
not self-explanatory.
- `help` content is contextual guidance, not the accessible name. The persisted
`show_inline_help_hints` user preference hides only the `InlineHelp` marker by
- applying `ui-hide-help-hints` at the document root.
+ applying `ui-hide-help-hints` at the document root. When shown, the shared
+ marker is a labelled, keyboard-focusable help control and exposes its tooltip
+ on focus as well as pointer hover.
- Shared action-bearing components accept an optional disabled reason. In
particular, `MailServerSettingsPanel` forwards protocol-specific test
blockers into the shared focusable disabled-action tooltip; modules provide
diff --git a/pyproject.toml b/pyproject.toml
index c00f520..a40dcb6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "govoplan-core"
-version = "0.1.40"
+version = "0.1.41"
description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components."
readme = "README.md"
requires-python = ">=3.12"
diff --git a/webui/conformance/ConformanceApp.tsx b/webui/conformance/ConformanceApp.tsx
index a3943d4..67300a4 100644
--- a/webui/conformance/ConformanceApp.tsx
+++ b/webui/conformance/ConformanceApp.tsx
@@ -1,6 +1,9 @@
import { useMemo, useState } from "react";
import { FileText, GitBranch, Inbox, Search, ShieldCheck } from "lucide-react";
import { useLocation } from "react-router";
+import FormInstancePage from "../../../govoplan-forms-runtime/webui/src/features/forms/FormInstancePage";
+import FormsRuntimePage from "../../../govoplan-forms-runtime/webui/src/features/forms/FormsRuntimePage";
+import PublicFormPage from "../../../govoplan-forms-runtime/webui/src/features/forms/PublicFormPage";
import QuickAccessRail from "../../../govoplan-quick-access/webui/src/components/QuickAccessRail";
import ActionToolbar from "../src/components/ActionToolbar";
import Button from "../src/components/Button";
@@ -40,6 +43,16 @@ export default function ConformanceApp() {
const [editorDirty, setEditorDirty] = useState(true);
const [metricDrilldown, setMetricDrilldown] = useState("");
+ if (location.pathname.startsWith("/forms/public/")) {
+ return ;
+ }
+ if (location.pathname === "/forms-runtime") {
+ return ;
+ }
+ if (location.pathname.startsWith("/forms-runtime/")) {
+ return ;
+ }
+
return (
-
+
-
+
+ } />
+ } />
+ } />
+
diff --git a/webui/conformance/tests/ui-conformance.spec.ts b/webui/conformance/tests/ui-conformance.spec.ts
index 30641e0..2094e17 100644
--- a/webui/conformance/tests/ui-conformance.spec.ts
+++ b/webui/conformance/tests/ui-conformance.spec.ts
@@ -19,6 +19,125 @@ async function expectNoAccessibilityViolations(page: import("@playwright/test").
expect(violations).toEqual([]);
}
+for (const viewport of [
+ { name: "desktop", width: 1280, height: 900 },
+ { name: "mobile", width: 390, height: 844 }
+]) {
+ test(`resident permit self-service is keyboard and accessibility conformant on ${viewport.name}`, async ({ page }) => {
+ const journey = await mockPublicResidentPermitJourney(page);
+ await page.setViewportSize(viewport);
+ await page.goto("/forms/public/resident-parking-permit?theme=light");
+
+ await expect(page.getByRole("heading", { level: 1, name: "Anwohnerparkausweis beantragen" })).toBeVisible();
+ await expectNoAccessibilityViolations(page);
+
+ const name = page.getByLabel("Name der antragstellenden Person");
+ await name.focus();
+ await page.keyboard.type("Ada Lovelace");
+ await page.keyboard.press("Tab");
+ await expect(page.getByLabel("E-Mail-Adresse")).toBeFocused();
+ await page.keyboard.type("ada.lovelace@example.test");
+ await page.keyboard.press("Tab");
+ await expect(page.getByLabel("Hauptwohnsitz")).toBeFocused();
+ await page.keyboard.type("Musterstraße 17, 10115 Berlin");
+ await page.keyboard.press("Tab");
+ await expect(page.getByLabel("Kfz-Kennzeichen")).toBeFocused();
+ await page.keyboard.type("B-AL 1843");
+ await page.keyboard.press("Tab");
+ await expect(page.getByRole("button", { name: "Entwurf speichern" })).toBeFocused();
+ await page.keyboard.press("Enter");
+ await expect.poll(() => journey.savedValues()).toEqual({
+ applicant_name: "Ada Lovelace",
+ applicant_email: "ada.lovelace@example.test",
+ residence_address: "Musterstraße 17, 10115 Berlin",
+ licence_plate: "B-AL 1843"
+ });
+
+ await page.getByRole("button", { name: "Absenden" }).click();
+ const confirm = page.getByRole("alertdialog", { name: "Formular absenden" });
+ await expect(confirm).toBeVisible();
+ await expectNoAccessibilityViolations(page);
+ await confirm.getByRole("button", { name: "Absenden" }).click();
+ await expect(page.getByText("Übermittlung eingegangen")).toBeVisible();
+ await expect(page.getByText("receipt-rpp-2026-0001")).toBeVisible();
+ await expectNoHorizontalOverflow(page);
+ });
+
+ test(`resident permit assisted intake preserves per-field provenance on ${viewport.name}`, async ({ page }) => {
+ const journey = await mockAssistedResidentPermitJourney(page);
+ await page.setViewportSize(viewport);
+ await page.goto("/forms-runtime?theme=light");
+
+ await page.getByRole("button", { name: "Assistierte Erfassung" }).click();
+ const startDialog = page.getByRole("dialog", { name: "Assistierte Erfassung starten" });
+ await expect(startDialog).toBeVisible();
+ await expectNoAccessibilityViolations(page);
+ await startDialog.getByLabel("Referenz der betroffenen Partei").fill("party:resident-ada-lovelace");
+ await startDialog.getByLabel("Referenz der zuständigen Funktion").fill("function:parking-permits");
+ await startDialog.getByLabel("Zweck").fill("Anwohnerparkausweis beantragen");
+ await startDialog.getByLabel("Referenz der Rechtsgrundlage").fill("law:resident-parking-permit");
+ await startDialog.getByLabel("Barrierefreiheits- oder Kommunikationsunterstützung").fill("Leichte Sprache");
+ const notice = startDialog.getByRole("checkbox", { name: "Datenschutz- und Verfahrenshinweis wurde erteilt" });
+ await notice.focus();
+ await page.keyboard.press("Space");
+ await expect(notice).toBeChecked();
+ await startDialog.getByLabel("Referenz der betroffenen Partei").focus();
+ await page.keyboard.press("Tab");
+ await expect(page.locator(":focus")).toHaveAttribute("aria-label", "Feldhilfe anzeigen");
+ await page.keyboard.press("Tab");
+ await expect(startDialog.getByLabel("Referenz der vertretenen Partei")).toBeFocused();
+ await startDialog.getByRole("button", { name: "Sitzung starten" }).click();
+
+ await expect(page).toHaveURL(/\/forms-runtime\/assisted-rpp-1$/);
+ await expect(page.getByRole("heading", { level: 1, name: "Anwohnerparkausweis beantragen" })).toBeVisible();
+ await page.getByLabel("Name der antragstellenden Person").fill("Ada Lovelace");
+ await page.getByLabel("E-Mail-Adresse").fill("ada.lovelace@example.test");
+ await page.getByLabel("Hauptwohnsitz").fill("Musterstraße 17, 10115 Berlin");
+ await page.getByLabel("Kfz-Kennzeichen").fill("B-AL 1843");
+ await page.getByLabel("Änderungsgrund").fill("Angaben gemeinsam mit der antragstellenden Person erfasst.");
+ await page.getByRole("button", { name: "Entwurf speichern" }).click();
+
+ await page.getByRole("button", { name: "Rücklesen und absenden" }).click();
+ const readback = page.getByRole("dialog", { name: "Assistiertes Rücklesen erfassen" });
+ await expect(readback).toBeVisible();
+ await expect(readback.getByRole("group", { name: "Hauptwohnsitz" })).toBeVisible();
+ await expectNoAccessibilityViolations(page);
+
+ const addressSource = readback.getByRole("group", { name: "Hauptwohnsitz" });
+ await addressSource.getByLabel("Wertquelle").selectOption("document");
+ await addressSource.getByLabel("Quellenvertrauen").selectOption("verified");
+ await addressSource.getByLabel("Erklärende Partei oder Quellenreferenz").fill("files:residence-proof-2026");
+ const plateSource = readback.getByRole("group", { name: "Kfz-Kennzeichen" });
+ await plateSource.getByLabel("Wertquelle").focus();
+ await page.keyboard.press("ArrowDown");
+ await page.keyboard.press("ArrowDown");
+ await page.keyboard.press("ArrowDown");
+ await page.keyboard.press("Enter");
+ await plateSource.getByLabel("Quellenvertrauen").selectOption("verified");
+ await plateSource.getByLabel("Erklärende Partei oder Quellenreferenz").fill("register:vehicle-B-AL-1843");
+ await readback.getByRole("button", { name: "Erfassen und fortfahren" }).click();
+
+ await expect.poll(() => journey.confirmationSources()).toMatchObject({
+ applicant_name: { source: "person_statement", confidence: "stated" },
+ residence_address: {
+ source: "document",
+ confidence: "verified",
+ declared_by_ref: "files:residence-proof-2026"
+ },
+ licence_plate: {
+ source: "system",
+ confidence: "verified",
+ declared_by_ref: "register:vehicle-B-AL-1843"
+ }
+ });
+ const submit = page.getByRole("alertdialog", { name: "Formular absenden" });
+ await submit.getByRole("button", { name: "Absenden" }).click();
+ await expect(page.getByText("receipt-assisted-rpp-2026-0001")).toBeVisible();
+ await expect(page.getByText("Rücklesen erfasst")).toBeVisible();
+ await expectNoHorizontalOverflow(page);
+ });
+}
+
test("shared components remain accessible and keyboard operable", async ({ page }) => {
await page.goto("/?theme=light");
await expect(page.getByRole("heading", { level: 1, name: "Zentrale GovOPlaN-Oberflächen" })).toBeVisible();
@@ -229,6 +348,301 @@ test("narrow layout preserves task order without horizontal overflow", async ({
await expect(page.locator("[data-conformance-id='shared-ui-lab']")).toHaveScreenshot("shared-ui-light-narrow.png", { animations: "disabled", maxDiffPixelRatio: 0.005 });
});
+async function expectNoHorizontalOverflow(page: import("@playwright/test").Page) {
+ const overflowing = await page.evaluate(() => Array.from(document.querySelectorAll("body *"))
+ .filter((element) => {
+ const style = window.getComputedStyle(element);
+ return style.display !== "none" && style.visibility !== "hidden";
+ })
+ .map((element) => {
+ const rect = element.getBoundingClientRect();
+ return {
+ element: `${element.tagName.toLowerCase()}.${Array.from(element.classList).join(".")}`,
+ left: Math.round(rect.left),
+ right: Math.round(rect.right)
+ };
+ })
+ .filter(({ left, right }) => left < -1 || right > window.innerWidth + 1)
+ .slice(0, 20));
+ expect(overflowing).toEqual([]);
+}
+
+async function mockPublicResidentPermitJourney(page: import("@playwright/test").Page) {
+ let current = residentPermitInstance("public-rpp-1", "started", 1, {});
+ let savedValues: Record = {};
+
+ await page.route("**/api/v1/forms-runtime/**", async (route) => {
+ const request = route.request();
+ const path = new URL(request.url()).pathname;
+ const method = request.method();
+ if (path.endsWith("/public/profiles/resident-parking-permit/start") && method === "POST") {
+ return fulfillJson(route, {
+ session_id: "session-public-rpp-1",
+ mode: "anonymous",
+ status: "active",
+ expires_at: "2026-08-25T10:00:00Z",
+ instance: current,
+ token: "public-rpp-token",
+ replayed: false
+ });
+ }
+ if (path.endsWith("/public/intake") && method === "GET") {
+ return fulfillJson(route, { instance: current, definition: residentPermitDefinition() });
+ }
+ if (path.endsWith("/public/intake") && method === "PATCH") {
+ const payload = request.postDataJSON() as { values: Record };
+ savedValues = payload.values;
+ current = residentPermitInstance("public-rpp-1", "draft", 2, payload.values);
+ return fulfillJson(route, current);
+ }
+ if (path.endsWith("/public/intake/submit") && method === "POST") {
+ const payload = request.postDataJSON() as { values: Record };
+ current = {
+ ...residentPermitInstance("public-rpp-1", "submitted", 3, payload.values),
+ receipt_id: "receipt-rpp-2026-0001"
+ };
+ return fulfillJson(route, current);
+ }
+ return route.abort("failed");
+ });
+
+ return { savedValues: () => savedValues };
+}
+
+async function mockAssistedResidentPermitJourney(page: import("@playwright/test").Page) {
+ let current = residentPermitInstance("assisted-rpp-1", "started", 1, {}, true);
+ let confirmationSources: Record = {};
+ let confirmations: unknown[] = [];
+
+ await page.route("**/api/v1/forms-runtime/**", async (route) => {
+ const request = route.request();
+ const path = new URL(request.url()).pathname;
+ const method = request.method();
+
+ if (path.endsWith("/instances") && method === "GET") {
+ return fulfillJson(route, { instances: [], total: 0, offset: 0, limit: 200 });
+ }
+ if (path.endsWith("/assisted-intake/profiles") && method === "GET") {
+ return fulfillJson(route, { profiles: [{
+ profile_id: "assisted-profile-rpp",
+ public_id: "resident-parking-permit",
+ definition_ref: residentPermitDefinition().reference,
+ mode: "assisted",
+ enabled: true,
+ revision: 1,
+ draft_ttl_seconds: 2_592_000,
+ invitation_ttl_seconds: 1_209_600,
+ rate_limit_per_minute: 60,
+ metadata: { definition_title: "Anwohnerparkausweis beantragen" }
+ }] });
+ }
+ if (path.endsWith("/assisted-intake/start") && method === "POST") {
+ return fulfillJson(route, {
+ session_id: "assisted-session-rpp-1",
+ mode: "assisted",
+ status: "active",
+ expires_at: "2026-08-25T10:00:00Z",
+ instance: current,
+ token: null,
+ replayed: false
+ });
+ }
+ if (path.endsWith("/instances/assisted-rpp-1/definition") && method === "GET") {
+ return fulfillJson(route, residentPermitDefinition());
+ }
+ if (path.endsWith("/instances/assisted-rpp-1/history") && method === "GET") {
+ return fulfillJson(route, { revisions: [current] });
+ }
+ if (path.endsWith("/instances/assisted-rpp-1/events") && method === "GET") {
+ return fulfillJson(route, { events: [{
+ event_id: `event-${current.revision}`,
+ event_type: current.status === "submitted" ? "submitted" : "draft_saved",
+ instance_revision: current.revision,
+ status: current.status,
+ occurred_at: current.recorded_at,
+ actor_id: "operator-1",
+ payload: {}
+ }] });
+ }
+ if (path.endsWith("/instances/assisted-rpp-1/handoffs") && method === "GET") {
+ return fulfillJson(route, { handoffs: [] });
+ }
+ if (path.endsWith("/instances/assisted-rpp-1/assisted-confirmations") && method === "GET") {
+ return fulfillJson(route, { confirmations });
+ }
+ if (path.endsWith("/instances/assisted-rpp-1/assisted-confirmations") && method === "POST") {
+ const payload = request.postDataJSON() as { field_sources: Record };
+ confirmationSources = payload.field_sources;
+ const confirmation = {
+ confirmation_id: "confirmation-rpp-1",
+ instance_id: "assisted-rpp-1",
+ instance_revision: current.revision,
+ outcome: "confirmed",
+ method: "spoken_readback",
+ confirmed_by_ref: "party:resident-ada-lovelace",
+ operator_actor_id: "operator-1",
+ confirmed_at: "2026-08-24T10:10:00Z",
+ payload_sha256: "a".repeat(64),
+ correction_note: null,
+ metadata: { field_sources: confirmationSources }
+ };
+ confirmations = [confirmation];
+ return fulfillJson(route, confirmation);
+ }
+ if (path.endsWith("/instances/assisted-rpp-1/submit") && method === "POST") {
+ const payload = request.postDataJSON() as { values: Record };
+ current = {
+ ...residentPermitInstance("assisted-rpp-1", "submitted", current.revision + 1, payload.values, true),
+ receipt_id: "receipt-assisted-rpp-2026-0001"
+ };
+ return fulfillJson(route, current);
+ }
+ if (path.endsWith("/instances/assisted-rpp-1") && method === "PATCH") {
+ const payload = request.postDataJSON() as { values: Record };
+ current = residentPermitInstance("assisted-rpp-1", "draft", current.revision + 1, payload.values, true);
+ return fulfillJson(route, current);
+ }
+ if (path.endsWith("/instances/assisted-rpp-1") && method === "GET") {
+ return fulfillJson(route, current);
+ }
+ return route.abort("failed");
+ });
+
+ return { confirmationSources: () => confirmationSources };
+}
+
+function residentPermitDefinition() {
+ return {
+ reference: {
+ kind: "form",
+ owner_module: "forms",
+ object_id: "resident-parking-permit-application",
+ tenant_id: "tenant-1",
+ version: "3",
+ label: "Anwohnerparkausweis beantragen"
+ },
+ key: "resident_parking_permit.apply",
+ temporal: { revision: "3", recorded_at: "2026-08-24T10:00:00Z" },
+ title: "Resident parking permit application",
+ description: "Apply digitally or together with an authorized service worker.",
+ fields: [
+ residentPermitField("applicant_name", "Applicant name", "text", { min_length: 2, max_length: 200 }),
+ residentPermitField("applicant_email", "Applicant email", "email", { format: "email" }),
+ residentPermitField("residence_address", "Primary residence", "text", { max_length: 500 }),
+ residentPermitField("licence_plate", "Licence plate", "text", { max_length: 20 })
+ ],
+ publication_state: "published",
+ allow_drafts: true,
+ max_attachments: 0,
+ signature_requirement: "none",
+ policy_refs: ["law:resident-parking-permit"],
+ handoff_kinds: [],
+ fallback_locale: "de",
+ localizations: [{
+ locale: "de",
+ title: "Anwohnerparkausweis beantragen",
+ description: "Beantragen Sie den Anwohnerparkausweis digital oder gemeinsam mit einer berechtigten Servicestelle.",
+ field_labels: {
+ applicant_name: "Name der antragstellenden Person",
+ applicant_email: "E-Mail-Adresse",
+ residence_address: "Hauptwohnsitz",
+ licence_plate: "Kfz-Kennzeichen"
+ },
+ field_help_texts: {},
+ option_labels: {},
+ page_titles: {},
+ section_titles: {}
+ }]
+ };
+}
+
+function residentPermitField(
+ key: string,
+ label: string,
+ valueType: "text" | "email",
+ constraints: Record
+) {
+ return {
+ key,
+ label,
+ value_type: valueType,
+ required: true,
+ help_text: null,
+ options: [],
+ constraints,
+ default_value: null,
+ visibility_condition: null
+ };
+}
+
+function residentPermitInstance(
+ instanceId: string,
+ status: string,
+ revision: number,
+ values: Record,
+ assisted = false
+) {
+ return {
+ reference: {
+ kind: "form_instance",
+ owner_module: "forms_runtime",
+ object_id: instanceId,
+ tenant_id: "tenant-1",
+ version: String(revision),
+ label: "Anwohnerparkausweis beantragen"
+ },
+ tenant_id: "tenant-1",
+ instance_id: instanceId,
+ revision,
+ status,
+ definition_ref: residentPermitDefinition().reference,
+ values,
+ validation_results: [],
+ attachment_refs: [],
+ signature_refs: [],
+ handoff_refs: [],
+ service_ref: null,
+ receipt_id: null as string | null,
+ recorded_at: "2026-08-24T10:00:00Z",
+ change_reason: revision === 1 ? "Assisted session started." : "Draft saved.",
+ created_by: "operator-1",
+ changed_by: "operator-1",
+ metadata: assisted ? {
+ intake: {
+ session_id: "assisted-session-rpp-1",
+ profile_id: "assisted-profile-rpp",
+ mode: "assisted",
+ channel: "counter",
+ affected_party_ref: "party:resident-ada-lovelace",
+ represented_party_ref: null,
+ authority_basis: "self",
+ purpose: "Anwohnerparkausweis beantragen",
+ legal_basis_ref: "law:resident-parking-permit",
+ consent_basis: "in-person-confirmation",
+ notice_given: true,
+ responsible_function_ref: "function:parking-permits",
+ language: "de",
+ accessibility_needs: ["Leichte Sprache"],
+ field_sources: {},
+ operator: { actor_id: "operator-1", auth_method: "session" }
+ }
+ } : {},
+ status_access: null,
+ replayed: false
+ };
+}
+
+async function fulfillJson(
+ route: import("@playwright/test").Route,
+ body: unknown
+) {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify(body)
+ });
+}
+
function quickAccessPayload(includeMessages: boolean) {
const files = {
id: "files",
diff --git a/webui/package-lock.json b/webui/package-lock.json
index f839c7e..07150c3 100644
--- a/webui/package-lock.json
+++ b/webui/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@govoplan/core-webui",
- "version": "0.1.40",
+ "version": "0.1.41",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@govoplan/core-webui",
- "version": "0.1.40",
+ "version": "0.1.41",
"dependencies": {
"@govoplan/access-webui": "file:../../govoplan-access/webui",
"@govoplan/addresses-webui": "file:../../govoplan-addresses/webui",
@@ -378,7 +378,7 @@
},
"../../govoplan-forms-runtime/webui": {
"name": "@govoplan/forms-runtime-webui",
- "version": "0.1.18",
+ "version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0",
diff --git a/webui/package-lock.release.json b/webui/package-lock.release.json
index f70c61d..b252175 100644
--- a/webui/package-lock.release.json
+++ b/webui/package-lock.release.json
@@ -1,12 +1,12 @@
{
"name": "@govoplan/core-webui",
- "version": "0.1.40",
+ "version": "0.1.41",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@govoplan/core-webui",
- "version": "0.1.40",
+ "version": "0.1.41",
"dependencies": {
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.23",
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.22",
diff --git a/webui/package.json b/webui/package.json
index eee3746..c67349c 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -1,6 +1,6 @@
{
"name": "@govoplan/core-webui",
- "version": "0.1.40",
+ "version": "0.1.41",
"private": true,
"type": "module",
"main": "src/index.ts",
diff --git a/webui/package.release.json b/webui/package.release.json
index c7b4d3f..cef0074 100644
--- a/webui/package.release.json
+++ b/webui/package.release.json
@@ -1,6 +1,6 @@
{
"name": "@govoplan/core-webui",
- "version": "0.1.40",
+ "version": "0.1.41",
"private": true,
"type": "module",
"main": "src/index.ts",
diff --git a/webui/src/components/HoverTooltip.tsx b/webui/src/components/HoverTooltip.tsx
index 5526204..cfb4d5c 100644
--- a/webui/src/components/HoverTooltip.tsx
+++ b/webui/src/components/HoverTooltip.tsx
@@ -207,6 +207,7 @@ export default function HoverTooltip({
+ triggerTabIndex={0}>
?
);
diff --git a/webui/src/i18n/generatedTranslations.ts b/webui/src/i18n/generatedTranslations.ts
index 7f8c3de..02aaee2 100644
--- a/webui/src/i18n/generatedTranslations.ts
+++ b/webui/src/i18n/generatedTranslations.ts
@@ -1276,8 +1276,8 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.share.09ca55ca": "Freigabe",
"i18n:govoplan-core.set_concrete_system_retention_values_blank_day_f.98b9a627": "Set concrete system retention values. Blank day fields mean unlimited retention.",
"i18n:govoplan-core.settings.c7f73bb5": "Einstellungen",
- "i18n:govoplan-core.show_content.0528d8d2": "Show content",
- "i18n:govoplan-core.show_field_help.e3dfe98f": "Show field help",
+ "i18n:govoplan-core.show_content.0528d8d2": "Inhalt anzeigen",
+ "i18n:govoplan-core.show_field_help.e3dfe98f": "Feldhilfe anzeigen",
"i18n:govoplan-core.show_guided_warnings_while_editing.bc5dba85": "Show guided warnings while editing",
"i18n:govoplan-core.show_header_only.24afefca": "Show header only",
"i18n:govoplan-core.show_inline_guidance_and_warnings_while_campaign.a892f5e9": "Show inline guidance and warnings while campaign data is being edited.",
diff --git a/webui/src/styles/badges.css b/webui/src/styles/badges.css
index b65f7a6..640e7da 100644
--- a/webui/src/styles/badges.css
+++ b/webui/src/styles/badges.css
@@ -1,5 +1,5 @@
.status-badge { display: inline-flex; align-items: center; height: 24px; border-radius: var(--radius-pill); padding: 0 9px; font-size: 12px; font-weight: 800; background: var(--status-neutral-bg); color: var(--text-soft); text-transform: uppercase; }
-.status-ready, .status-sent, .status-appended, .status-success, .status-active { background: var(--success-soft); color: var(--success-text-strong); }
+.status-ready, .status-sent, .status-appended, .status-success, .status-active { background: var(--success-soft); color: var(--success-text); }
.status-warning, .status-needs-review, .status-pending { background: var(--warning-soft); color: var(--warning-text-strong); }
.status-blocked, .status-error, .status-danger, .status-failed, .status-failed-permanent { background: var(--danger-bg); color: var(--danger-text-strong); }
.status-queued, .status-sending { background: var(--info-soft); color: var(--info-text-strong); }