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", reference="docs/DOCUMENTATION_LAYER_CONCEPT.md",
summary="Defines the manifest-driven documentation boundary.", 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=( known_limits=(
"Architecture declarations are in staged adoption, so undeclared modules remain visible as pending.", "Architecture declarations are in staged adoption, so undeclared modules remain visible as pending.",
+89 -86
View File
@@ -8,10 +8,10 @@ import {
Dialog, Dialog,
DismissibleAlert, DismissibleAlert,
ExplorerTree, ExplorerTree,
LoadingFrame, PageLayout,
PageTitle,
SegmentedControl, SegmentedControl,
StatusBadge, StatusBadge,
WorkspaceLayout,
adminErrorMessage, adminErrorMessage,
useGuardedNavigate, useGuardedNavigate,
usePlatformLanguage, usePlatformLanguage,
@@ -133,92 +133,95 @@ export default function DocsPage({ settings }: { settings: ApiSettings }) {
}, [treeNodes, selectedPage?.id]); }, [treeNodes, selectedPage?.id]);
return ( return (
<div className="workspace module-workspace docs-workspace"> <WorkspaceLayout
<aside className="section-sidebar docs-outline" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99"> className="module-workspace docs-workspace"
<div className="docs-sidebar-header"> primarySize="wide"
<div className="section-title">i18n:govoplan-docs.help_center.f3f3a34b</div> primaryLabel="i18n:govoplan-docs.documentation_outline.6f836b99"
<AudienceToggle contentLabel="i18n:govoplan-docs.help_center.f3f3a34b"
selected={documentationType} documentationType={documentationType}
onSelect={selectDocumentationType} contentClassName="docs-workspace-content"
canViewAdmin={context?.actor.available_documentation_types.includes("admin") ?? documentationType === "admin"} primary={(
/> <aside className="section-sidebar docs-outline" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99">
<label className="docs-version-selector" title={context?.versions.fallback_policy}> <div className="docs-sidebar-header">
<span>Version</span> <div className="section-title">i18n:govoplan-docs.help_center.f3f3a34b</div>
<select <AudienceToggle
value={selectedVersion ?? ""} selected={documentationType}
onChange={(event) => selectVersion(event.target.value || null)} onSelect={selectDocumentationType}
> canViewAdmin={context?.actor.available_documentation_types.includes("admin") ?? documentationType === "admin"}
<option value="">Installed versions</option> />
{selectedVersion && !context?.versions.supported_versions.includes(selectedVersion) && <label className="docs-version-selector" title={context?.versions.fallback_policy}>
<option value={selectedVersion}>{selectedVersion} (unsupported)</option> <span>Version</span>
} <select
{(context?.versions.supported_versions ?? []).map((version) => ( value={selectedVersion ?? ""}
<option key={version} value={version}>{version}</option> onChange={(event) => selectVersion(event.target.value || null)}
))} >
</select> <option value="">Installed versions</option>
</label> {selectedVersion && !context?.versions.supported_versions.includes(selectedVersion) &&
</div> <option value={selectedVersion}>{selectedVersion} (unsupported)</option>
<nav className="docs-tree" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99"> }
<ExplorerTree {(context?.versions.supported_versions ?? []).map((version) => (
nodes={treeNodes} <option key={version} value={version}>{version}</option>
getNodeId={(node) => node.id} ))}
getNodeLabel={(node) => node.title} </select>
getNodeChildren={(node) => node.children} </label>
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>
</div> </div>
<nav className="docs-tree" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99">
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>} <ExplorerTree
nodes={treeNodes}
<LoadingFrame loading={loading} label="i18n:govoplan-docs.loading_documentation_context.1c091645"> getNodeId={(node) => node.id}
<div className="docs-content"> getNodeLabel={(node) => node.title}
<main className="docs-page-main"> getNodeChildren={(node) => node.children}
<SelectedPageContent activeId={activeNodeIdForPage(treeNodes, selectedPage?.id ?? "")}
page={selectedPage} expandedIds={expandedNodes}
adminDocs={adminDocs} depth={0}
documentationType={documentationType} childrenBaseClassName="docs-tree-children"
topicById={topicById} nodeContainerClassName="docs-tree-node"
moduleRows={moduleRows} nodeWrapBaseClassName="docs-tree-row"
visibleRoutes={visibleRoutes} toggleBaseClassName="docs-tree-toggle"
availableRoutes={availableRoutes} nodeButtonBaseClassName="docs-tree-page"
grantedPermissions={grantedPermissions} getNodeWrapStyle={(_node, context) => ({ paddingLeft: `${context.depth * 14}px` })}
evidenceModules={context?.layers.evidence.optional_modules ?? []} getNodeButtonClassName={(node) => selectedPage?.id === node.page.id ? "is-active" : ""}
evidenceSources={context?.layers.evidence.sources ?? []} renderToggleIcon={(_node, context) => context.hasChildren ? context.expanded ? <ChevronDown size={15} /> : <ChevronRight size={15} /> : <span className="docs-tree-toggle-placeholder" />}
settings={settings} renderNodeContent={(node) => node.title}
locale={locale} onToggle={(node) => toggleNode(node.id)}
/> onOpen={(node) => selectPage(node.page)}
</main> />
<PageOutline items={outlineItems} /> </nav>
</div> </aside>
</LoadingFrame> )}
>
<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> </div>
</section> </PageLayout>
</div> </WorkspaceLayout>
); );
function selectDocumentationType(type: DocumentationType) { function selectDocumentationType(type: DocumentationType) {