From e9ff29297b98c8e0e88522e013162dd91ba8f2d9 Mon Sep 17 00:00:00 2001
From: DCCONSTRUCTIONS
Date: Sun, 26 Jul 2026 17:06:13 +0300
Subject: [PATCH] feat(lab): standardize evidence workspace template
---
apps/control-station/src/App.tsx | 2 +
.../control-station/src/styles/workspaces.css | 49 +++++-
.../src/workspaces/Workspaces.tsx | 157 +++++++++++++-----
.../test/productShellContract.test.mjs | 52 ++++++
4 files changed, 215 insertions(+), 45 deletions(-)
create mode 100644 apps/control-station/test/productShellContract.test.mjs
diff --git a/apps/control-station/src/App.tsx b/apps/control-station/src/App.tsx
index a7bc5fb..04da62a 100644
--- a/apps/control-station/src/App.tsx
+++ b/apps/control-station/src/App.tsx
@@ -315,6 +315,8 @@ export default function App() {
const observationFullscreenActive = Boolean(
activeDefinition?.kind === "spatial"
? observationLayout.maximizedFloatingSourceId || focusedObservationSource?.modality === "point-cloud"
+ : activeDefinition?.kind === "lab-archive"
+ ? observationLayout.maximizedFloatingSourceId || focusedObservationSource?.modality === "point-cloud"
: activeDefinition?.kind === "cameras" && focusedObservationSource,
);
diff --git a/apps/control-station/src/styles/workspaces.css b/apps/control-station/src/styles/workspaces.css
index d58b18b..ee6d16d 100644
--- a/apps/control-station/src/styles/workspaces.css
+++ b/apps/control-station/src/styles/workspaces.css
@@ -2385,7 +2385,8 @@
.recordings-workspace,
.lab-archive-workspace,
-.lab-result-surface {
+.lab-result-surface,
+.laboratory-work-template {
display: grid;
gap: 0.85rem;
}
@@ -2542,12 +2543,52 @@
white-space: nowrap;
}
-.lab-result-surface {
+.laboratory-evidence-frame {
+ position: relative;
+ min-width: 0;
+ min-height: 0;
+ overflow: hidden;
+ border-radius: 1rem;
+}
+
+.lab-result-surface[data-resizable="true"] .laboratory-evidence-frame {
+ height: clamp(42rem, 68vh, 58rem);
+ min-height: 42rem;
+ resize: vertical;
+}
+
+.laboratory-evidence-frame > .spatial-workspace {
+ height: 100%;
min-height: 0;
}
-.lab-result-surface .spatial-workspace {
- min-height: 34rem;
+.laboratory-evidence-frame > .lidar-quality-workspace {
+ min-height: 42rem;
+}
+
+.lab-archive-workspace[data-viewer-focused="true"] {
+ height: 100%;
+ min-height: 0;
+ padding: 0;
+}
+
+.lab-archive-workspace[data-viewer-focused="true"] > .laboratory-selector,
+.lab-archive-workspace[data-viewer-focused="true"]
+ .laboratory-work-template > :not(.lab-result-surface),
+.lab-archive-workspace[data-viewer-focused="true"] .lab-result-surface > header {
+ display: none;
+}
+
+.lab-archive-workspace[data-viewer-focused="true"] > .laboratory-work-output,
+.lab-archive-workspace[data-viewer-focused="true"] .laboratory-work-template,
+.lab-archive-workspace[data-viewer-focused="true"] .lab-result-surface,
+.lab-archive-workspace[data-viewer-focused="true"] .laboratory-evidence-frame {
+ height: 100%;
+ min-height: 0;
+}
+
+.lab-archive-workspace[data-viewer-focused="true"] .laboratory-evidence-frame {
+ resize: none;
}
.laboratory-result-pending {
diff --git a/apps/control-station/src/workspaces/Workspaces.tsx b/apps/control-station/src/workspaces/Workspaces.tsx
index 3c5fc37..5bc1318 100644
--- a/apps/control-station/src/workspaces/Workspaces.tsx
+++ b/apps/control-station/src/workspaces/Workspaces.tsx
@@ -5,6 +5,7 @@ import {
useRef,
useState,
type ComponentType,
+ type ReactNode,
} from "react";
import {
Button,
@@ -1281,6 +1282,54 @@ function LaboratoryTask({
);
}
+function LaboratoryEvidence({
+ eyebrow,
+ title,
+ resizable = false,
+ children,
+}: {
+ eyebrow: string;
+ title: string;
+ resizable?: boolean;
+ children: ReactNode;
+}) {
+ return (
+
+ );
+}
+
+function LaboratoryWorkTemplate({
+ task,
+ evidence,
+ result = null,
+ details = null,
+}: {
+ task: ReactNode;
+ evidence: ReactNode;
+ result?: ReactNode;
+ details?: ReactNode;
+}) {
+ return (
+
+ {task}
+ {evidence}
+ {result}
+ {details}
+
+ );
+}
+
function formatSeconds(value: number): string {
return `${value.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} с`;
}
@@ -1334,8 +1383,9 @@ function E29LaboratoryResult({
}, [result.resultId, selectedFrameIndex]);
return (
- <>
-
-
-
-
- ИСХОДНЫЕ ДАННЫЕ
- LiDAR, траектория и камера RAVNOVES00
-
+ />
+ )}
+ evidence={(
+
{replayReady ? (
) : (
@@ -1374,9 +1425,10 @@ function E29LaboratoryResult({
)}
-
-
-
+
+ )}
+ result={(
+
РЕЗУЛЬТАТ И ВЫВОД
@@ -1409,9 +1461,10 @@ function E29LaboratoryResult({
{result.decision.nextGate}
-
-
-
+ )}
+ details={(
+
- >
+
+ )}
+ />
);
}
@@ -1524,8 +1578,9 @@ function PublishedLaboratoryResult({
const replayReady = props.recordedReplay?.sessionId === session.id;
return (
- <>
-
-
-
-
- ВИЗУАЛЬНОЕ ДОКАЗАТЕЛЬСТВО
- Исходная запись выбранной лабораторной работы
-
+ />
+ )}
+ evidence={(
+
{replayReady ? (
) : (
@@ -1555,9 +1611,10 @@ function PublishedLaboratoryResult({
)}
-
-
-
+
+ )}
+ result={(
+
- >
+
+ )}
+ />
);
}
@@ -1792,8 +1850,16 @@ function LabArchiveWorkspace(props: WorkspaceRendererProps) {
);
}
+ const viewerFocused = Boolean(
+ props.observationLayout.focusedSourceId
+ || props.observationLayout.maximizedFloatingSourceId,
+ );
+
return (
-
+
profile.id === profileId)?.label ?? rigLabel}
@@ -1817,8 +1883,9 @@ function LabArchiveWorkspace(props: WorkspaceRendererProps) {
{workId === "e28-local-surface" ? (
- <>
-
- props.navigation.openView("spatial-scene")}
- />
- >
+ />
+ )}
+ evidence={(
+
+ props.navigation.openView("spatial-scene")}
+ />
+
+ )}
+ />
) : workId === "e29-camera-geometry" && e29Result && e29SourceSession ? (
{
+ server = await createServer({
+ appType: "custom",
+ logLevel: "silent",
+ server: { middlewareMode: true },
+ });
+ productModel = await server.ssrLoadModule("/src/productModel.ts");
+});
+
+after(async () => {
+ await server?.close();
+});
+
+test("top navigation has no Center and Park owns contour health first", () => {
+ assert.deepEqual(
+ productModel.roots.map((root) => root.id),
+ ["fleet", "observation", "missions", "data", "system", "polygon"],
+ );
+ assert.equal(productModel.workspacesForRoot("fleet")[0]?.id, "contour-health");
+ assert.equal(productModel.workspaceById("contour-health")?.root, "fleet");
+});
+
+test("every laboratory result uses the shared evidence template", async () => {
+ const source = await readFile(
+ new URL("../src/workspaces/Workspaces.tsx", import.meta.url),
+ "utf8",
+ );
+ const css = await readFile(
+ new URL("../src/styles/workspaces.css", import.meta.url),
+ "utf8",
+ );
+ const app = await readFile(
+ new URL("../src/App.tsx", import.meta.url),
+ "utf8",
+ );
+
+ assert.match(source, /function LaboratoryWorkTemplate\(/);
+ assert.match(source, /function LaboratoryEvidence\(/);
+ assert.match(source, /data-viewer-focused=/);
+ assert.match(css, /height:\s*clamp\(42rem,\s*68vh,\s*58rem\)/);
+ assert.match(css, /resize:\s*vertical/);
+ assert.match(app, /activeDefinition\?\.kind === "lab-archive"/);
+});