diff --git a/README.md b/README.md index a23cf9c..8aad9fb 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,13 @@ cd /mnt/DATA/git/govoplan Feature content remains in the owning module. Docs indexes and renders the contributions without importing feature implementations. +Pressing `F1` resolves the focused field or action first, then its containing +dialog or section, current page, and owning module. The shell sends the focused +context together with `fallback_context` and `module`; Docs selects the first +visible exact, page-level, or module-level topic after applying audience and +permission filtering. Modules announce exact route and control identities in +static topic `metadata.help_contexts`. + Capabilities can provide generic documentation without exposing their runtime provider implementation: diff --git a/src/govoplan_docs/backend/manifest.py b/src/govoplan_docs/backend/manifest.py index 58761d8..1547263 100644 --- a/src/govoplan_docs/backend/manifest.py +++ b/src/govoplan_docs/backend/manifest.py @@ -263,6 +263,69 @@ manifest = ModuleManifest( ], }, ), + DocumentationTopic( + id="docs.pattern.contextual-help", + title="Context-sensitive help", + summary="Press F1 on a page, field, action, or dialog to open help for the current interface context.", + body=( + "GovOPlaN first resolves help for the focused field or action, then its dialog or section, " + "the current page, and the owning module. Exact documentation is shown when available; " + "otherwise the page or module documentation is used. The titlebar help button opens the " + "current page context. Documentation remains filtered by the current account's audience, " + "permissions, and configured modules." + ), + layer="always", + documentation_types=("admin", "user"), + audience=("user", "tenant_admin", "operator", "module_admin"), + order=21, + i18n_key="docs.topic.pattern.contextual_help", + links=( + DocumentationLink( + label="Contextual help contract", + href="govoplan-core/docs/CONTEXTUAL_HELP_CONTRACT.md", + kind="repository", + ), + ), + metadata={ + "kind": "pattern", + "pattern_id": "contextual-f1-help", + "help_contexts": [ + "core.contextual-help", + "core.titlebar.language", + ], + "component_refs": [ + "govoplan-core/webui/src/layout/HelpMenu.tsx", + "govoplan-core/webui/src/utils/helpContext.ts", + ], + }, + ), + DocumentationTopic( + id="docs.reference.temporal-data-context", + title="Temporal data context", + summary="Choose whether pages show currently valid records, records valid at a selected time, or all valid-time states.", + body=( + "The titlebar calendar controls valid time across participating modules. Current is the neutral " + "default. At time selects records valid at the chosen instant, while All includes historical and " + "future valid-time states. Recorded time remains separate: it describes when the platform learned " + "or stored a fact. Permissions are evaluated now, so temporal selection never restores historical access." + ), + layer="always", + documentation_types=("admin", "user"), + audience=("user", "tenant_admin", "operator", "module_admin"), + order=22, + i18n_key="docs.topic.reference.temporal_data_context", + links=( + DocumentationLink( + label="Temporal data read contract", + href="govoplan-core/docs/TEMPORAL_DATA_CONTEXT.md", + kind="repository", + ), + ), + metadata={ + "kind": "reference", + "help_contexts": ["core.temporal-data-context"], + }, + ), DocumentationTopic( id="docs.reference.organization-identity-idm-access-boundary", title="Organization, identity, IDM, and access boundary", @@ -278,7 +341,7 @@ manifest = ModuleManifest( documentation_types=("admin", "user"), audience=("tenant_admin", "access_admin", "operator", "user"), related_modules=("organizations", "identity", "idm", "access"), - order=21, + order=23, conditions=( DocumentationCondition( required_modules=("organizations", "identity", "idm", "access"), diff --git a/webui/src/features/docs/DocsPage.tsx b/webui/src/features/docs/DocsPage.tsx index 6a1fb45..a687b39 100644 --- a/webui/src/features/docs/DocsPage.tsx +++ b/webui/src/features/docs/DocsPage.tsx @@ -857,9 +857,12 @@ function selectedPageFromSearch(search: string, pages: DocsPageNode[]): DocsPage if (requested) return pages.find((page) => page.id === requested) ?? pages[0]; const helpContext = params.get("context") || ""; if (helpContext) { - const exact = pages.find((page) => page.kind === "topic" && metadataList(page.topic.metadata, "help_contexts").includes(helpContext)); - if (exact) return exact; - const moduleId = helpContextModuleId(helpContext); + const fallbackContext = params.get("fallback_context") || ""; + for (const contextId of [helpContext, fallbackContext].filter(Boolean)) { + const exact = pages.find((page) => page.kind === "topic" && metadataList(page.topic.metadata, "help_contexts").includes(contextId)); + if (exact) return exact; + } + const moduleId = params.get("module") || helpContextModuleId(fallbackContext || helpContext); const moduleTopic = pages.find((page) => page.kind === "topic" && page.topic.source_module_id === moduleId); if (moduleTopic) return moduleTopic; }