Adopt shared WebUI structural primitives

This commit is contained in:
2026-08-18 13:17:31 +02:00
parent 0b4e7b6391
commit e05d5b4a54
3 changed files with 119 additions and 86 deletions
+25
View File
@@ -0,0 +1,25 @@
# Docs Interface Pattern Migration
The Docs route composes the platform interface language without owning a
parallel shell. Core owns its navigation/content pane geometry and page frame;
Docs owns audience filtering, version selection, topic navigation, configured
documentation projection, and evidence presentation.
## Surface contract
- `WorkspaceLayout` owns the outline/content regions, pane width, scrolling,
accessible labels, and narrow-layout navigation behavior.
- `PageLayout` owns the content inset, sticky heading, audience-aware page
identity, reload action placement, error and loading regions, and responsive
action flow.
- `ExplorerTree`, `SegmentedControl`, `DataGrid`, `DescriptionList`, dialogs,
alerts, and status badges retain their Core interaction and accessibility
contracts.
- Docs-specific CSS remains limited to the documentation tree, reading column,
topic typography, page outline, and reference content. It no longer defines
the outer workspace grid or page skeleton.
The module keeps user/admin audience boundaries, installed-version selection,
topic URLs, configured-state projection, and evidence visibility unchanged.
The platform layout checker rejects restoring a raw Docs page or workspace
frame.
+5
View File
@@ -47,6 +47,11 @@ ARCHITECTURE = ModuleArchitectureDeclaration(
reference="docs/DOCUMENTATION_LAYER_CONCEPT.md",
summary="Defines the manifest-driven documentation boundary.",
),
ModuleMaturityEvidence(
kind="documentation",
reference="docs/INTERFACE_PATTERN_MIGRATION.md",
summary="Records the Core-owned Docs workspace and page-layout boundary.",
),
),
known_limits=(
"Architecture declarations are in staged adoption, so undeclared modules remain visible as pending.",
+89 -86
View File
@@ -8,10 +8,10 @@ import {
Dialog,
DismissibleAlert,
ExplorerTree,
LoadingFrame,
PageTitle,
PageLayout,
SegmentedControl,
StatusBadge,
WorkspaceLayout,
adminErrorMessage,
useGuardedNavigate,
usePlatformLanguage,
@@ -133,92 +133,95 @@ export default function DocsPage({ settings }: { settings: ApiSettings }) {
}, [treeNodes, selectedPage?.id]);
return (
<div className="workspace module-workspace docs-workspace">
<aside className="section-sidebar docs-outline" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99">
<div className="docs-sidebar-header">
<div className="section-title">i18n:govoplan-docs.help_center.f3f3a34b</div>
<AudienceToggle
selected={documentationType}
onSelect={selectDocumentationType}
canViewAdmin={context?.actor.available_documentation_types.includes("admin") ?? documentationType === "admin"}
/>
<label className="docs-version-selector" title={context?.versions.fallback_policy}>
<span>Version</span>
<select
value={selectedVersion ?? ""}
onChange={(event) => selectVersion(event.target.value || null)}
>
<option value="">Installed versions</option>
{selectedVersion && !context?.versions.supported_versions.includes(selectedVersion) &&
<option value={selectedVersion}>{selectedVersion} (unsupported)</option>
}
{(context?.versions.supported_versions ?? []).map((version) => (
<option key={version} value={version}>{version}</option>
))}
</select>
</label>
</div>
<nav className="docs-tree" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99">
<ExplorerTree
nodes={treeNodes}
getNodeId={(node) => node.id}
getNodeLabel={(node) => node.title}
getNodeChildren={(node) => node.children}
activeId={activeNodeIdForPage(treeNodes, selectedPage?.id ?? "")}
expandedIds={expandedNodes}
depth={0}
childrenBaseClassName="docs-tree-children"
nodeContainerClassName="docs-tree-node"
nodeWrapBaseClassName="docs-tree-row"
toggleBaseClassName="docs-tree-toggle"
nodeButtonBaseClassName="docs-tree-page"
getNodeWrapStyle={(_node, context) => ({ paddingLeft: `${context.depth * 14}px` })}
getNodeButtonClassName={(node) => selectedPage?.id === node.page.id ? "is-active" : ""}
renderToggleIcon={(_node, context) => context.hasChildren ? context.expanded ? <ChevronDown size={15} /> : <ChevronRight size={15} /> : <span className="docs-tree-toggle-placeholder" />}
renderNodeContent={(node) => node.title}
onToggle={(node) => toggleNode(node.id)}
onOpen={(node) => selectPage(node.page)}
/>
</nav>
</aside>
<section className="workspace-content docs-workspace-content">
<div className="content-pad workspace-data-page docs-page">
<div className="page-heading split workspace-heading">
<div>
<PageTitle loading={loading}>i18n:govoplan-docs.help_center.f3f3a34b</PageTitle>
<p>{adminDocs ? "i18n:govoplan-docs.technical_documentation_for_the_modules_and_conf.267e739e" : "i18n:govoplan-docs.guidance_for_the_functions_available_in_this_ins.723b7cad"}</p>
</div>
<div className="button-row compact-actions">
<Button onClick={() => void load()} disabled={loading}><RefreshCw size={16} /> i18n:govoplan-docs.reload.cce71553</Button>
</div>
<WorkspaceLayout
className="module-workspace docs-workspace"
primarySize="wide"
primaryLabel="i18n:govoplan-docs.documentation_outline.6f836b99"
contentLabel="i18n:govoplan-docs.help_center.f3f3a34b"
documentationType={documentationType}
contentClassName="docs-workspace-content"
primary={(
<aside className="section-sidebar docs-outline" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99">
<div className="docs-sidebar-header">
<div className="section-title">i18n:govoplan-docs.help_center.f3f3a34b</div>
<AudienceToggle
selected={documentationType}
onSelect={selectDocumentationType}
canViewAdmin={context?.actor.available_documentation_types.includes("admin") ?? documentationType === "admin"}
/>
<label className="docs-version-selector" title={context?.versions.fallback_policy}>
<span>Version</span>
<select
value={selectedVersion ?? ""}
onChange={(event) => selectVersion(event.target.value || null)}
>
<option value="">Installed versions</option>
{selectedVersion && !context?.versions.supported_versions.includes(selectedVersion) &&
<option value={selectedVersion}>{selectedVersion} (unsupported)</option>
}
{(context?.versions.supported_versions ?? []).map((version) => (
<option key={version} value={version}>{version}</option>
))}
</select>
</label>
</div>
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
<LoadingFrame loading={loading} label="i18n:govoplan-docs.loading_documentation_context.1c091645">
<div className="docs-content">
<main className="docs-page-main">
<SelectedPageContent
page={selectedPage}
adminDocs={adminDocs}
documentationType={documentationType}
topicById={topicById}
moduleRows={moduleRows}
visibleRoutes={visibleRoutes}
availableRoutes={availableRoutes}
grantedPermissions={grantedPermissions}
evidenceModules={context?.layers.evidence.optional_modules ?? []}
evidenceSources={context?.layers.evidence.sources ?? []}
settings={settings}
locale={locale}
/>
</main>
<PageOutline items={outlineItems} />
</div>
</LoadingFrame>
<nav className="docs-tree" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99">
<ExplorerTree
nodes={treeNodes}
getNodeId={(node) => node.id}
getNodeLabel={(node) => node.title}
getNodeChildren={(node) => node.children}
activeId={activeNodeIdForPage(treeNodes, selectedPage?.id ?? "")}
expandedIds={expandedNodes}
depth={0}
childrenBaseClassName="docs-tree-children"
nodeContainerClassName="docs-tree-node"
nodeWrapBaseClassName="docs-tree-row"
toggleBaseClassName="docs-tree-toggle"
nodeButtonBaseClassName="docs-tree-page"
getNodeWrapStyle={(_node, context) => ({ paddingLeft: `${context.depth * 14}px` })}
getNodeButtonClassName={(node) => selectedPage?.id === node.page.id ? "is-active" : ""}
renderToggleIcon={(_node, context) => context.hasChildren ? context.expanded ? <ChevronDown size={15} /> : <ChevronRight size={15} /> : <span className="docs-tree-toggle-placeholder" />}
renderNodeContent={(node) => node.title}
onToggle={(node) => toggleNode(node.id)}
onOpen={(node) => selectPage(node.page)}
/>
</nav>
</aside>
)}
>
<PageLayout
title="i18n:govoplan-docs.help_center.f3f3a34b"
description={adminDocs ? "i18n:govoplan-docs.technical_documentation_for_the_modules_and_conf.267e739e" : "i18n:govoplan-docs.guidance_for_the_functions_available_in_this_ins.723b7cad"}
actions={<Button onClick={() => void load()} disabled={loading}><RefreshCw size={16} /> i18n:govoplan-docs.reload.cce71553</Button>}
loading={loading}
loadingLabel="i18n:govoplan-docs.loading_documentation_context.1c091645"
error={error}
mode="workspace"
documentationType={documentationType}
className="docs-page"
>
<div className="docs-content">
<main className="docs-page-main">
<SelectedPageContent
page={selectedPage}
adminDocs={adminDocs}
documentationType={documentationType}
topicById={topicById}
moduleRows={moduleRows}
visibleRoutes={visibleRoutes}
availableRoutes={availableRoutes}
grantedPermissions={grantedPermissions}
evidenceModules={context?.layers.evidence.optional_modules ?? []}
evidenceSources={context?.layers.evidence.sources ?? []}
settings={settings}
locale={locale}
/>
</main>
<PageOutline items={outlineItems} />
</div>
</section>
</div>
</PageLayout>
</WorkspaceLayout>
);
function selectDocumentationType(type: DocumentationType) {