diff --git a/docs/LOCALIZATION_AND_HELP_QUALITY.md b/docs/LOCALIZATION_AND_HELP_QUALITY.md index bfe683a..5787526 100644 --- a/docs/LOCALIZATION_AND_HELP_QUALITY.md +++ b/docs/LOCALIZATION_AND_HELP_QUALITY.md @@ -48,6 +48,12 @@ module and server restrictions, lower-scope visibility, activation, save, and irreversible deletion. This ensures F1 explains secret custody and the effect on dependent connections from system, tenant, group, user, and personal surfaces. +The source inventory treats literal `helpContextId` and +`data-help-context-id` declarations as authored help associations, including a +native control nested in `FormField`. Dynamic context expressions remain +separate evidence and generic derived fallbacks remain in the richer-help +candidate queue. + The generated `help_review_candidates` list is therefore a content-depth queue, not a list of controls on which F1 cannot work. It should prioritize: @@ -57,6 +63,14 @@ not a list of controls on which F1 cannot work. It should prioritize: 4. provider authority, synchronization, conflict, and outcome unknown; 5. fields whose consequences are not evident from their label. +The shared browser conformance journey mounts the production Help menu and +resolver. It proves that F1 uses the focused control rather than only the page, +maps an exact retention action to Policy-owned administrator documentation, +retains the page context as fallback for derived actions, exposes an accessible +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. + ## Verification ```bash @@ -74,3 +88,10 @@ The check must report: - no duplicate stable IDs; - no undeclared public WebUI surface; - no stale runtime route or endpoint declaration. + +Browser acceptance is part of the focused workspace gate and can be run alone: + +```bash +cd /mnt/DATA/git/govoplan-core/webui +npm run test:conformance +``` diff --git a/webui/conformance/ConformanceApp.tsx b/webui/conformance/ConformanceApp.tsx index 57bed16..a3943d4 100644 --- a/webui/conformance/ConformanceApp.tsx +++ b/webui/conformance/ConformanceApp.tsx @@ -26,6 +26,7 @@ import WorkspaceFrame from "../src/components/WorkspaceFrame"; import WorkspaceLayout from "../src/components/WorkspaceLayout"; import WorkspaceActionBar from "../src/components/WorkspaceActionBar"; import BreadcrumbBar from "../src/layout/BreadcrumbBar"; +import HelpMenu from "../src/layout/HelpMenu"; import { useGuardedNavigate } from "../src/components/UnsavedChangesGuard"; import { createQuickAccessLaunchContext, @@ -149,6 +150,7 @@ export default function ConformanceApp() { + {new URLSearchParams(location.search).has("help") ? : null} +

Kontextsensitive Hilfe

+

F1 löst den Hilfekontext des fokussierten Bedienelements auf und kehrt nach dem Schließen dorthin zurück.

+ + + + + + + ); +} + function QuickAccessScenario() { const location = useLocation(); const mode = new URLSearchParams(location.search).get("quick-access"); diff --git a/webui/conformance/installedModules.ts b/webui/conformance/installedModules.ts new file mode 100644 index 0000000..7abf182 --- /dev/null +++ b/webui/conformance/installedModules.ts @@ -0,0 +1,3 @@ +const installedModules: Array = []; + +export default installedModules; diff --git a/webui/conformance/main.tsx b/webui/conformance/main.tsx index 911f907..9334220 100644 --- a/webui/conformance/main.tsx +++ b/webui/conformance/main.tsx @@ -3,6 +3,9 @@ import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router"; import ConformanceApp from "./ConformanceApp"; import { UnsavedChangesProvider } from "../src/components/UnsavedChangesGuard"; +import { PlatformLanguageProvider } from "../src/i18n/LanguageContext"; +import { PlatformModulesProvider } from "../src/platform/ModuleContext"; +import type { PlatformWebModule } from "../src/types"; import "../src/styles/tokens.css"; import "../src/styles/layout.css"; import "../src/styles/forms.css"; @@ -16,12 +19,28 @@ import "./conformance.css"; const theme = new URLSearchParams(window.location.search).get("theme"); if (theme === "dark" || theme === "light") document.documentElement.dataset.theme = theme; +const CONFORMANCE_MODULES: PlatformWebModule[] = [{ + id: "policy", + label: "Richtlinien", + version: "1", + helpContexts: [{ + id: "policy.retention.action.apply", + topic_id: "policy.admin.retention-and-disposition", + title: "Aufbewahrungsregeln sicher anwenden", + documentation_types: ["admin"] + }] +}]; + ReactDOM.createRoot(document.getElementById("root")!).render( - - - + + + + + + + ); diff --git a/webui/conformance/tests/ui-conformance.spec.ts b/webui/conformance/tests/ui-conformance.spec.ts index db031dd..30641e0 100644 --- a/webui/conformance/tests/ui-conformance.spec.ts +++ b/webui/conformance/tests/ui-conformance.spec.ts @@ -110,6 +110,44 @@ test("Quick Access preserves focus and adapts to a narrow viewport", async ({ pa await expect(filesTrigger).toBeFocused(); }); +test("F1 resolves focused high-risk help and restores focus accessibly", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/?theme=light&help=1"); + + const retentionAction = page.getByTestId("f1-retention-action"); + await retentionAction.focus(); + await page.keyboard.press("F1"); + + const dialog = page.getByRole("dialog", { name: "Kontexthilfe" }); + await expect(dialog).toBeVisible(); + await expect(dialog.locator("[data-help-context]")) + .toHaveAttribute("data-help-context", "policy.retention.action.apply"); + await expect(dialog.getByRole("heading", { level: 3 })) + .toHaveText("Aufbewahrungsregeln anwenden"); + await expect(dialog).toContainText("topic=policy.admin.retention-and-disposition"); + await expect(dialog.getByRole("button", { name: "Administrator-Dokumentation öffnen" })) + .toBeVisible(); + await expectNoAccessibilityViolations(page); + + const bounds = await dialog.boundingBox(); + expect(bounds?.x).toBeGreaterThanOrEqual(0); + expect((bounds?.x ?? 0) + (bounds?.width ?? 0)).toBeLessThanOrEqual(390); + + await page.keyboard.press("Escape"); + await expect(dialog).toBeHidden(); + await expect(retentionAction).toBeFocused(); + + const fallbackAction = page.getByTestId("f1-fallback-action"); + await fallbackAction.focus(); + await page.keyboard.press("F1"); + const fallbackDialog = page.getByRole("dialog", { name: "Kontexthilfe" }); + await expect(fallbackDialog.locator("[data-help-context]")) + .toHaveAttribute("data-help-context", "core.conformance.action.review"); + await expect(fallbackDialog).toContainText("fallback_context=campaigns.list"); + await page.keyboard.press("Escape"); + await expect(fallbackAction).toBeFocused(); +}); + test("View focus has a deliberate permission-derived all-tools escape", async ({ page }) => { await page.route("**/api/v1/quick-access/effective*", async (route) => { await route.fulfill({ diff --git a/webui/src/i18n/generatedTranslations.ts b/webui/src/i18n/generatedTranslations.ts index 8327722..7f8c3de 100644 --- a/webui/src/i18n/generatedTranslations.ts +++ b/webui/src/i18n/generatedTranslations.ts @@ -26,6 +26,7 @@ export const generatedTranslations: PlatformTranslations = { "i18n:govoplan-core.restore_safe_defaults": "Restore safe defaults", "i18n:govoplan-core.import_overrides": "Import JSON", "i18n:govoplan-core.export_overrides": "Export JSON", + "i18n:govoplan-core.explanation_subject.6ff46d22": "Explanation subject", "i18n:govoplan-core.remove_overrides": "Remove overrides", "i18n:govoplan-core.appearance_override_not_configured_help": "No personal token overrides are stored. Preset and policy inheritance remain active.", "i18n:govoplan-core.appearance_override_policy_disabled": "The governing appearance policy currently blocks creating or editing personal overrides. Existing overrides may still be exported or removed.", @@ -761,6 +762,7 @@ export const generatedTranslations: PlatformTranslations = { "i18n:govoplan-core.restore_safe_defaults": "Sichere Standardwerte wiederherstellen", "i18n:govoplan-core.import_overrides": "JSON importieren", "i18n:govoplan-core.export_overrides": "JSON exportieren", + "i18n:govoplan-core.explanation_subject.6ff46d22": "Person für die Zugriffserklärung", "i18n:govoplan-core.remove_overrides": "Anpassungen entfernen", "i18n:govoplan-core.appearance_override_not_configured_help": "Es sind keine persönlichen Token-Anpassungen gespeichert. Vorgaben und Richtlinienvererbung bleiben aktiv.", "i18n:govoplan-core.appearance_override_policy_disabled": "Die geltende Darstellungsrichtlinie sperrt derzeit das Anlegen oder Bearbeiten persönlicher Anpassungen. Bestehende Anpassungen können weiterhin exportiert oder entfernt werden.", @@ -936,7 +938,7 @@ export const generatedTranslations: PlatformTranslations = { "i18n:govoplan-core.choose_date.e7877fd9": "Choose date", "i18n:govoplan-core.clear_filter.b667d6f9": "Clear filter", "i18n:govoplan-core.close_filter.3a281c3f": "Close filter", - "i18n:govoplan-core.close.bbfa773e": "Close", + "i18n:govoplan-core.close.bbfa773e": "Schließen", "i18n:govoplan-core.collapse.9cf188d3": "Collapse", "i18n:govoplan-core.color_picker.91dee962": "Color picker", "i18n:govoplan-core.comfortable.2313707a": "Comfortable", @@ -950,7 +952,7 @@ export const generatedTranslations: PlatformTranslations = { "i18n:govoplan-core.connection_security_mode_expected_by_the_server_.4348e12e": "Connection security mode expected by the server: plain, TLS or STARTTLS.", "i18n:govoplan-core.connection_successful_the_backend_health_endpoin.14eb9295": "Connection successful. The backend health endpoint responded.", "i18n:govoplan-core.contains.33e15d00": "Contains", - "i18n:govoplan-core.context_help.7fc44ea1": "Context help", + "i18n:govoplan-core.context_help.7fc44ea1": "Kontexthilfe", "i18n:govoplan-core.controls_contextual_ui_help_markers_once_persist.0eaef9c4": "Controls contextual UI help markers once persisted user preferences are available.", "i18n:govoplan-core.core_only.d46ce7d9": "Core only", "i18n:govoplan-core.core_shell_only.4949b5d5": "Core shell only", @@ -1047,9 +1049,9 @@ export const generatedTranslations: PlatformTranslations = { "i18n:govoplan-core.group.171a0606": "Group", "i18n:govoplan-core.groups": "Gruppen", "i18n:govoplan-core.headers.520de744": "Headers", - "i18n:govoplan-core.help_context.61aed3b9": "Help context:", + "i18n:govoplan-core.help_context.61aed3b9": "Hilfekontext:", "i18n:govoplan-core.help_f1": "Hilfe (F1)", - "i18n:govoplan-core.help.c47ae153": "Help", + "i18n:govoplan-core.help.c47ae153": "Hilfe", "i18n:govoplan-core.hh_mm.a4c7ee9b": "HH:MM", "i18n:govoplan-core.hide_password.e40123b4": "Hide password", "i18n:govoplan-core.high_level_campaign_pattern_used_by_guided_setup.c5ec6652": "High-level campaign pattern used by guided setup to prefill sensible defaults.", @@ -1174,9 +1176,9 @@ export const generatedTranslations: PlatformTranslations = { "i18n:govoplan-core.on.e0049a66": "On", "i18n:govoplan-core.only_users_with_maintenance_access_can_use_the_s.5566264f": "Only users with maintenance access can use the system right now.", "i18n:govoplan-core.open_address_form.f8ee560f": "Open address form", - "i18n:govoplan-core.open_admin_documentation.6adbdae3": "Open admin documentation", + "i18n:govoplan-core.open_admin_documentation.6adbdae3": "Administrator-Dokumentation öffnen", "i18n:govoplan-core.open_maintenance_mode_settings.99b41249": "Open maintenance mode settings", - "i18n:govoplan-core.open_user_documentation.084af515": "Open user documentation", + "i18n:govoplan-core.open_user_documentation.084af515": "Benutzerdokumentation öffnen", "i18n:govoplan-core.optional_backend_url_leave_empty_to_use_the_curr.52bd2bb4": "Optional backend URL. Leave empty to use the current origin and the configured Vite/backend proxy.", "i18n:govoplan-core.optional_html_version_of_the_email_body_for_rich.3f01db97": "Optional HTML version of the email body for richer formatting.", "i18n:govoplan-core.optional_local_alias_for_the_active_tenant_it_do.edbb7b14": "Optional local alias for the active tenant. It does not replace the global account name in the title bar.", @@ -1184,7 +1186,7 @@ export const generatedTranslations: PlatformTranslations = { "i18n:govoplan-core.owner.89ff3122": "Eigentümer", "i18n:govoplan-core.owner_policy.1e8df143": "Owner policy", "i18n:govoplan-core.p_no_html_body_content_p.c305bfc6": "

No HTML body content.

", - "i18n:govoplan-core.page.fb06270f": "Page", + "i18n:govoplan-core.page.fb06270f": "Seite", "i18n:govoplan-core.password_for_the_selected_account_or_mail_server.d9681f16": "Password for the selected account or mail server. Stored and transmitted according to the configured backend flow.", "i18n:govoplan-core.password_protected.09d9e174": "Password protected", "i18n:govoplan-core.password.8be3c943": "Passwort", @@ -1346,12 +1348,12 @@ export const generatedTranslations: PlatformTranslations = { "i18n:govoplan-core.theme_and_language.60a8cf59": "Design und Sprache", "i18n:govoplan-core.theme.a797e309": "Theme", "i18n:govoplan-core.this_address_is_already_listed.0f6d0ff2": "This address is already listed.", - "i18n:govoplan-core.this_area_is_prepared_for_context_sensitive_help.57665877": "This area is prepared for context-sensitive help. Future help content can use this context identifier, or the equivalent help query parameter", + "i18n:govoplan-core.this_area_is_prepared_for_context_sensitive_help.57665877": "Das fokussierte Bedienelement verweist auf den folgenden dokumentierten Hilfekontext:", "i18n:govoplan-core.this_global_name_is_shown_in_the_title_bar_and_f.1d46240e": "This global name is shown in the title bar and follows you across tenant memberships.", "i18n:govoplan-core.this_module_is_prepared_but_not_implemented_yet.6e6449ee": "Dieses Modul ist vorbereitet, aber noch nicht umgesetzt.", "i18n:govoplan-core.this_page_has_unsaved_changes_save_them_before_l.419a9d8b": "This page has unsaved changes. Save them before leaving, or discard the changes and continue.", "i18n:govoplan-core.timeout_seconds.0bfc6553": "Timeout seconds", - "i18n:govoplan-core.to_open_the_right_page_or_section.5ecf4fd2": "to open the right page or section.", + "i18n:govoplan-core.to_open_the_right_page_or_section.5ecf4fd2": "Öffnen Sie die verknüpfte Dokumentation für Berechtigungen, Folgen und Hinweise zur Wiederherstellung.", "i18n:govoplan-core.to.ae79ea1e": "To", "i18n:govoplan-core.tu.b44892b7": "Tu", "i18n:govoplan-core.type_a_name_and_email_address_then_press_enter.7c8d43f0": "Type a name and email address, then press Enter", diff --git a/webui/vite.conformance.config.ts b/webui/vite.conformance.config.ts index 18158db..ce087ba 100644 --- a/webui/vite.conformance.config.ts +++ b/webui/vite.conformance.config.ts @@ -8,6 +8,7 @@ export default defineConfig({ resolve: { alias: { "@govoplan/core-webui": resolve(import.meta.dirname, "conformance/QuickAccessCoreFacade.ts"), + "virtual:govoplan-installed-modules": resolve(import.meta.dirname, "conformance/installedModules.ts"), react: resolve(import.meta.dirname, "node_modules/react"), "react-router": resolve(import.meta.dirname, "node_modules/react-router"), "lucide-react": resolve(import.meta.dirname, "node_modules/lucide-react")