feat: compose stable product surfaces
Module Package Release / publish-packages (push) Successful in 13s

This commit is contained in:
2026-08-24 13:21:24 +02:00
parent f98cf9ced8
commit 6e518fa6a2
25 changed files with 1012 additions and 17 deletions
+22
View File
@@ -19,6 +19,7 @@ import MetricGrid from "../src/components/MetricGrid";
import PageActionBar from "../src/components/PageActionBar";
import SelectionList, { SelectionListItem, SelectionListItemContent } from "../src/components/SelectionList";
import StatePanel from "../src/components/StatePanel";
import ProductAvailabilityState from "../src/components/ProductAvailabilityState";
import WorkspaceLayout from "../src/components/WorkspaceLayout";
import WorkspaceFrame from "../src/components/WorkspaceFrame";
import WorkspaceActionBar from "../src/components/WorkspaceActionBar";
@@ -183,6 +184,27 @@ assert(workspaceMarkup.includes("selection-list-navigation"), "resource navigati
assert(workspaceMarkup.includes("selection-list-item-content"), "selection-list copy owns title and description typography");
assert(workspaceMarkup.includes("state-panel-size-fill"), "whole-surface states share sizing and action anatomy");
const availabilityMarkup = renderToStaticMarkup(
<PlatformLanguageProvider>
<ProductAvailabilityState
state="degraded"
explanation={{
reason: "provider_degraded",
title: "Messages are delayed",
description: "Saved messages remain available, but new provider results may be delayed.",
resolution: "Retry later or contact the integration operator.",
responsibleRole: "Integration operator"
}}
technical={{ moduleId: "mail", providerId: "smtp-primary", correlationId: "event-1" }}
actions={<button type="button">Retry</button>}
/>
</PlatformLanguageProvider>
);
assert(availabilityMarkup.includes("product-availability-degraded"), "product availability uses one semantic state primitive");
assert(availabilityMarkup.includes("Retry later or contact the integration operator."), "availability states include an actionable recovery path");
assert(availabilityMarkup.includes("<details"), "technical provenance remains available on demand");
assert(availabilityMarkup.includes("smtp-primary"), "technical details preserve exact provider provenance");
const frameMarkup = renderToStaticMarkup(
<PlatformLanguageProvider><WorkspaceFrame as="main" height="viewport" label="Planning workspace" surface="panel"><span>Body</span></WorkspaceFrame></PlatformLanguageProvider>
);
+67
View File
@@ -18,6 +18,7 @@ import {
visibleRoutesForProjection
} from "../src/platform/views";
import { groupNavigationItems } from "../src/platform/productAreas";
import { composeProductSurfaces } from "../src/platform/productSurfaces";
import { hasAnyScope, scopeGrants } from "../src/utils/permissions";
function assert(condition: unknown, message: string): void {
@@ -193,6 +194,72 @@ assert(
"flat navigation should retain every authorized destination"
);
const productSurfaceExplanation = {
reason: "authorization" as const,
title: "Messages are unavailable",
description: "No message source is available for the current responsibility.",
resolution: "Ask the responsible administrator to review the assignment."
};
const messageSurfaceModules: PlatformWebModule[] = [
{
id: "mail",
label: "Mail",
version: "test",
productSurfaces: [{
contractVersion: "1",
id: "communication.messages",
moduleId: "mail",
label: "Messages",
description: "Read messages without merging channel custody.",
iconName: "mail",
entryPath: "/messages",
routePath: "/mail",
surfaceIds: ["mail.route.mail"],
presentations: ["task", "reader"],
capabilityIds: [],
searchSourceIds: ["mail.mailbox_messages"],
helpContextIds: ["mail.quick_access.messages"],
documentationTopicIds: ["mail.quick-access-and-product-area"],
allOf: [],
anyOf: ["mail:mailbox:read"],
aliases: ["/inbox"],
order: 10,
unavailable: productSurfaceExplanation
}]
},
{
id: "postbox",
label: "Postbox",
version: "test",
productSurfaceMetadata: [{
contract_version: "1",
id: "communication.messages",
module_id: "postbox",
label: "Messages",
description: "Read messages without merging channel custody.",
icon: "mail",
entry_path: "/messages",
route_path: "/postbox",
surface_ids: ["postbox.route.postbox"],
presentations: ["task", "reader"],
capability_ids: [],
search_source_ids: ["postbox.messages"],
help_context_ids: ["postbox.quick_access.messages"],
documentation_topic_ids: ["postbox.quick-access-and-product-area"],
required_all: [],
required_any: ["postbox:message:read"],
aliases: ["/inbox"],
order: 20,
unavailable: productSurfaceExplanation
}]
}
];
const composedMessages = composeProductSurfaces(messageSurfaceModules);
assert(composedMessages.length === 1, "related owner routes should compose into one product identity");
assert(composedMessages[0]?.entryPath === "/messages", "the composed identity should keep its stable entry path");
assert(composedMessages[0]?.contributors.map((item) => item.moduleId).join(",") === "mail,postbox", "composition should retain ordered technical provenance");
assert(composedMessages[0]?.aliases.join(",") === "/inbox", "migration aliases should be de-duplicated across owners");
const viewAwareFiles: PlatformWebModule = {
...files,
navItems: [{ to: "/files", label: "Files", order: 20, anyOf: ["files:file:read"] }],