fix(manager): match Mission Core landing geometry

This commit is contained in:
DCCONSTRUCTIONS
2026-08-22 10:23:07 +03:00
parent 506d786ee6
commit 06cb780777
6 changed files with 230 additions and 45 deletions
@@ -61,6 +61,29 @@ test("production Compose provides exact persistent writable presentation storage
assert.ok(!client.includes("#b9ff4a"));
});
test("Device Core overview follows the Mission Core landing-stage geometry", async () => {
const styles = await readFile(
new URL("../src/styles.css", import.meta.url),
"utf8",
);
for (const expected of [
".device-manager-stage {\n height: 100%;\n overflow: hidden;",
"left: clamp(2rem, 5vw, 6rem);",
"width: min(39rem, 50%);",
"transform: translateY(-55%);",
"linear-gradient(90deg, rgb(5 6 8 / 0.82) 0%, rgb(5 6 8 / 0.54) 46%, rgb(5 6 8 / 0.24) 100%)",
"font-size: clamp(3rem, 7vw, 7.6rem);",
]) assert.ok(styles.includes(expected), expected);
assert.ok(!/\.device-manager-home\s*\{[^}]*max-width:\s*1420px/s.test(styles));
const client = await readFile(
new URL("../src/DeviceManagerApp.tsx", import.meta.url),
"utf8",
);
assert.ok(client.includes('className="device-manager-home"'));
assert.ok(client.includes('data-has-media={hasMedia ? "true" : undefined}'));
assert.ok(!client.includes('<GlassSurface className="device-manager-home"'));
});
test("legacy single teaser migrates into the environment media playlist", () => {
const environment = normalizeEnvironmentPresentation({
defaultTeaser: {
+8 -2
View File
@@ -684,6 +684,9 @@ function DeviceStage({
&& primarySections.some((section) => section.value === value)
&& values.indexOf(value) === index
));
const hasMedia = overview.background.enabled && overview.background.items.some(
(item) => Boolean(mediaSource(item)) && Boolean(item.mediaKind),
);
return (
<div className="device-manager-stage">
{error ? (
@@ -694,7 +697,10 @@ function DeviceStage({
</GlassSurface>
) : null}
<GlassSurface className="device-manager-home" padding="lg" tone="soft">
<section
className="device-manager-home"
data-has-media={hasMedia ? "true" : undefined}
>
<EnvironmentBackdrop background={overview.background} />
<div className="device-manager-home__scrim" aria-hidden="true" />
<section className="device-manager-hero">
@@ -715,7 +721,7 @@ function DeviceStage({
</div>
) : null}
</section>
</GlassSurface>
</section>
</div>
);
}
+63 -31
View File
@@ -257,46 +257,56 @@ body {
.device-manager-stage {
height: 100%;
overflow: auto;
padding: clamp(1rem, 2.5vw, 2.5rem);
scrollbar-width: thin;
overflow: hidden;
}
.device-manager-home {
position: relative;
display: flex;
min-height: calc(100% - 1px);
max-width: 1420px;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
flex-direction: column;
justify-content: center;
gap: clamp(3rem, 7vh, 6rem);
border-radius: var(--nodedc-radius-card);
background: var(--nodedc-canvas);
isolation: isolate;
margin: 0 auto;
padding: clamp(2.5rem, 6vw, 6rem) !important;
}
.device-manager-home__media,
.device-manager-home__scrim {
position: absolute;
z-index: -2;
inset: 0;
width: 100%;
height: 100%;
}
.device-manager-home__media {
z-index: 0;
display: block;
object-fit: cover;
}
.device-manager-home__scrim {
z-index: -1;
background: var(--nodedc-overlay-bg);
opacity: 0.82;
z-index: 1;
background:
linear-gradient(90deg, rgb(5 6 8 / 0.82) 0%, rgb(5 6 8 / 0.54) 46%, rgb(5 6 8 / 0.24) 100%),
linear-gradient(0deg, rgb(5 6 8 / 0.58), transparent 38%);
pointer-events: none;
}
.device-manager-home:not([data-has-media="true"]) .device-manager-home__scrim {
background:
radial-gradient(circle at 68% 42%, rgb(255 255 255 / 0.035), transparent 34%),
linear-gradient(90deg, rgb(5 6 8 / 0.2), transparent 62%);
}
.device-manager-hero {
max-width: 900px;
position: absolute;
z-index: 2;
top: 50%;
left: clamp(2rem, 5vw, 6rem);
width: min(39rem, 50%);
transform: translateY(-55%);
}
.device-manager-hero__icon {
@@ -316,27 +326,33 @@ body {
}
.device-manager-hero h1 {
margin: 0.7rem 0 0.9rem;
margin: 0.75rem 0 1rem;
color: var(--nodedc-text-primary);
font-size: clamp(4rem, 8vw, 8.5rem);
font-weight: 520;
letter-spacing: -0.065em;
line-height: 0.9;
font-size: clamp(3rem, 7vw, 7.6rem);
font-weight: 600;
letter-spacing: -0.072em;
line-height: 0.87;
}
.device-manager-hero p {
max-width: 680px;
max-width: 34rem;
margin: 0;
color: var(--nodedc-text-secondary);
font-size: clamp(0.96rem, 1.4vw, 1.14rem);
line-height: 1.55;
font-size: clamp(0.8rem, 1vw, 1rem);
line-height: 1.6;
}
.device-manager-hero__actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.5rem;
gap: 0.65rem;
margin-top: 1.6rem;
}
@media (max-width: 1480px) {
.device-manager-hero {
width: min(35rem, 54%);
}
}
.environment-settings {
@@ -948,20 +964,36 @@ body {
gap: 1rem;
}
@media (max-height: 720px) and (min-width: 761px) {
.device-manager-hero h1 {
font-size: clamp(2.7rem, 6vw, 5rem);
}
.device-manager-hero p {
font-size: 0.76rem;
}
.device-manager-hero__actions {
margin-top: 1rem;
}
}
@media (max-width: 760px) {
body { overflow: auto; }
.device-manager-stage {
padding: 1rem;
overflow: hidden;
}
.device-manager-home {
min-height: auto;
padding: 2.5rem 1.25rem !important;
.device-manager-hero {
top: 42%;
right: 1.2rem;
left: 1.2rem;
width: auto;
}
.device-manager-hero h1 {
font-size: clamp(3.4rem, 17vw, 6rem);
font-size: clamp(2.8rem, 14vw, 5rem);
}
.device-manager-panel-toolbar,
+40
View File
@@ -0,0 +1,40 @@
{
"schemaVersion": "nodedc.device-plane.device-manager-release.v5",
"releaseId": "__PATCH_ID__",
"action": "upgrade",
"predecessor": {
"kind": "release",
"patchId": "device-manager-release-v4-20260822-033",
"artifactSha256": "52ba322042f1e4f595bbfea99f8bb35630b15984e0da648dc55348bc9e5b2066"
},
"controlCorePredecessor": {
"patchId": "device-control-core-release-v2-20260821-030",
"artifactSha256": "8459521a662541a5a87cb0188991cdcfb51727427db8ec2a232ce4846bfc3454"
},
"edgeChannelPredecessor": {
"patchId": "device-edge-core-channel-upgrade-v4-20260812-023",
"artifactSha256": "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
},
"service": "device-manager",
"publicIngress": "reverse-proxy-only",
"deviceCoreManagementApi": "file-token-authenticated",
"launcherTrust": "file-token-scoped-to-device-core-handoff",
"edgeChannel": "preserve-active-v4-core-initiated-pinned-mtls",
"edgeChannelIdentity": "reuse-runner-managed-host-local-private-key-public-certificate-export",
"edgeChannelEgress": "preserve-dedicated-core-only-bridge-no-host-ingress-public-ipv4-tcp-443-only",
"healthGate": "bounded-container-grace+core-contract+persistent-data",
"commandTransport": "typed-service-ping-v1",
"commandCatalog": "allowlisted-adapter-typed-commands-only",
"credentialBoundary": "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned",
"presentationPersistence": "runner-managed-host-data-bind",
"presentationDataHostPath": "/volume1/docker/nodedc-device-plane/data/device-manager",
"presentationDataContainerPath": "/var/lib/nodedc-device-manager",
"presentationDataOwnership": "uid-1000-gid-1000-mode-0750",
"presentationDataLifecycle": "preserve-across-manager-recreate-and-source-rollback",
"presentationPath": "/var/lib/nodedc-device-manager/device-manager-presentation.json",
"mediaRoot": "/var/lib/nodedc-device-manager/media",
"defaultAccentHex": "#f5f5f5",
"overviewLayout": "mission-core-landing-stage-v1",
"gelios": "untouched-legacy-only",
"rollback": "restore-preapply-snapshot-preserve-manager-data"
}
@@ -11,21 +11,25 @@ const platformRoot = resolve(scriptDir, "../..");
const devicePlaneRoot = platformRoot;
const managerRoot = resolve(platformRoot, "apps/device-manager");
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
const [patchId = "device-manager-release-v3-20260812-026", ...extra] = process.argv.slice(2);
const [patchId = "device-manager-release-v5-20260822-034", ...extra] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) throw new Error("usage: build-device-manager-control-plane-artifact.mjs [patch-id]");
const descriptorPath = patchId.startsWith("device-manager-release-v4-")
? "deployment/device-manager-release-v4.json"
: patchId.startsWith("device-manager-release-v3-")
? "deployment/device-manager-release-v3.json"
: "deployment/device-manager-release-v1.json";
const descriptorPath = patchId.startsWith("device-manager-release-v5-")
? "deployment/device-manager-release-v5.json"
: patchId.startsWith("device-manager-release-v4-")
? "deployment/device-manager-release-v4.json"
: patchId.startsWith("device-manager-release-v3-")
? "deployment/device-manager-release-v3.json"
: "deployment/device-manager-release-v1.json";
const isV3 = descriptorPath.endsWith("release-v3.json");
const isV4 = descriptorPath.endsWith("release-v4.json");
const isManagerOnly = isV3 || isV4;
const isV5 = descriptorPath.endsWith("release-v5.json");
const isPersistent = isV4 || isV5;
const isManagerOnly = isV3 || isPersistent;
const composeSource = resolve(devicePlaneRoot, "docker-compose.device-manager.yml");
const composeSourceSha256 = createHash("sha256").update(await readFile(composeSource)).digest("hex");
if (!isV4 && composeSourceSha256 !== "4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e") {
if (!isPersistent && composeSourceSha256 !== "4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e") {
throw new Error("historical_device_manager_compose_has_advanced");
}
const entries = isManagerOnly ? [
@@ -120,7 +124,7 @@ try {
"NODEDC_LAUNCHER_INTERNAL_TOKEN_FILE: /run/nodedc-secrets/device-core-internal-token",
"NODEDC_DEVICE_CORE_TOKEN_FILE: /run/nodedc-secrets/management-core-token",
"name: nodedc-platform_edge",
...(isV4 ? [
...(isPersistent ? [
"NODEDC_DEVICE_MANAGER_PRESENTATION_PATH: /var/lib/nodedc-device-manager/device-manager-presentation.json",
"NODEDC_DEVICE_MANAGER_MEDIA_ROOT: /var/lib/nodedc-device-manager/media",
"source: /volume1/docker/nodedc-device-plane/data/device-manager",
@@ -152,15 +156,40 @@ try {
|| !/^[A-Za-z0-9._-]{1,96}$/.test(predecessor.patchId || "")
|| !/^[a-f0-9]{64}$/.test(predecessor.artifactSha256 || "")
|| (descriptor.action === "activate") !== (predecessor.kind === "reconciliation")
|| descriptor.healthGate !== (isV4
|| descriptor.healthGate !== (isPersistent
? "bounded-container-grace+core-contract+persistent-data"
: "bounded-container-grace+core-contract")
|| descriptor.rollback !== (isV4
|| descriptor.rollback !== (isPersistent
? "restore-preapply-snapshot-preserve-manager-data"
: "restore-preapply-snapshot")
);
if (commonContractInvalid) throw new Error("device_manager_activation_successor_contract_mismatch");
if (descriptorPath.endsWith("release-v4.json")) {
if (descriptorPath.endsWith("release-v5.json")) {
if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v5"
|| descriptor.predecessor?.kind !== "release"
|| descriptor.predecessor?.patchId !== "device-manager-release-v4-20260822-033"
|| descriptor.predecessor?.artifactSha256 !== "52ba322042f1e4f595bbfea99f8bb35630b15984e0da648dc55348bc9e5b2066"
|| descriptor.commandTransport !== "typed-service-ping-v1"
|| descriptor.commandCatalog !== "allowlisted-adapter-typed-commands-only"
|| descriptor.credentialBoundary !== "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned"
|| descriptor.controlCorePredecessor?.patchId !== "device-control-core-release-v2-20260821-030"
|| descriptor.controlCorePredecessor?.artifactSha256 !== "8459521a662541a5a87cb0188991cdcfb51727427db8ec2a232ce4846bfc3454"
|| descriptor.edgeChannelPredecessor?.patchId !== "device-edge-core-channel-upgrade-v4-20260812-023"
|| descriptor.edgeChannelPredecessor?.artifactSha256 !== "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|| descriptor.presentationPersistence !== "runner-managed-host-data-bind"
|| descriptor.presentationDataHostPath !== "/volume1/docker/nodedc-device-plane/data/device-manager"
|| descriptor.presentationDataContainerPath !== "/var/lib/nodedc-device-manager"
|| descriptor.presentationDataOwnership !== "uid-1000-gid-1000-mode-0750"
|| descriptor.presentationDataLifecycle !== "preserve-across-manager-recreate-and-source-rollback"
|| descriptor.presentationPath !== "/var/lib/nodedc-device-manager/device-manager-presentation.json"
|| descriptor.mediaRoot !== "/var/lib/nodedc-device-manager/media"
|| descriptor.defaultAccentHex !== "#f5f5f5"
|| descriptor.overviewLayout !== "mission-core-landing-stage-v1"
|| descriptor.rollback !== "restore-preapply-snapshot-preserve-manager-data"
|| descriptor.gelios !== "untouched-legacy-only"
) throw new Error("device_manager_v5_overview_layout_contract_mismatch");
} else if (descriptorPath.endsWith("release-v4.json")) {
if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v4"
|| descriptor.commandTransport !== "typed-service-ping-v1"
@@ -448,6 +448,61 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
)
)
def test_device_manager_release_v5_matches_mission_core_landing_geometry(self):
patch_id = "device-manager-release-v5-unit-001"
manifest, entries, names, result = self.assert_deterministic_artifact(
"build-device-manager-control-plane-artifact.mjs",
patch_id,
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES,
)
self.assertEqual(manifest["component"], "device-plane")
self.assertEqual(result["services"], ["device-manager"])
self.assertFalse(
any(name.startswith("payload/services/device-control-core/") for name in names)
)
self.assertFalse(any(name.startswith("payload/packages/") for name in names))
self.assertFalse(any(name.endswith((".test.mjs", ".map")) for name in names))
template = json.loads(
(
DEVICE_CORE_ROOT
/ "deployment/device-manager-release-v5.json"
).read_text(encoding="utf-8")
)
descriptor = {**template, "releaseId": patch_id}
self.assertIs(
RUNNER.validate_device_plane_manager_release_descriptor(
descriptor,
schema_version=(
"nodedc.device-plane.device-manager-release.v5"
),
boundaries=(
RUNNER.expected_device_plane_manager_release_v5_boundaries()
),
expected_release_id=patch_id,
),
descriptor,
)
self.assertEqual(
descriptor["overviewLayout"],
"mission-core-landing-stage-v1",
)
self.assertEqual(
descriptor["predecessor"],
{
"kind": "release",
"patchId": "device-manager-release-v4-20260822-033",
"artifactSha256": (
"52ba322042f1e4f595bbfea99f8bb35630b15984e0da648dc55348bc9e5b2066"
),
},
)
self.assertTrue(
RUNNER.is_device_plane_manager_release_v5_slice(
"device-plane",
entries,
)
)
def test_historical_manager_builder_fails_closed_after_v4_compose(self):
if self.historical_manager_compose_is_current():
self.skipTest("historical Manager Compose is still current")