+
+
+
{definition.eyebrow}
+
{definition.title}
+
+ Записанные источники и строго связанные результаты без запуска тяжёлого
+ визуализатора и без доступа к управлению аппаратом.
+
+
+
+ {catalogStateLabel(controller.state)}
+
+
+
+
+
+
ИСТОЧНИК ДОКАЗАТЕЛЬСТВ
+
Сохранённая сессия
+
Выбор меняет только читаемую карточку и не готовит Rerun-запись в фоне.
+
+
+
+ }
+ onClick={controller.refresh}
+ >
+ Обновить
+
+
+
+
+ {controller.error && controller.catalog ? (
+
+ Показан последний срез
+ {controller.error}
+
+
+ ) : null}
+
+ {controller.catalog
+ && (controller.catalog.window.sourceLimitReached
+ || controller.catalog.window.laboratoryLimitReached) ? (
+
+ Срез ограничен
+
+ Загружены последние {controller.catalog.window.sourceCount} исходных сессий и{
+ " "
+ }{controller.catalog.window.laboratoryCount} LAB-результатов. Полнота исторических
+ связей не подтверждена.
+
+
+ ) : null}
+
+ {initialLoading ? (
+
+
+ Читаем источники и связи результатов
+ Визуализатор и лабораторные сцены при этом не запускаются.
+
+ ) : unavailable ? (
+
+
+ Каталог недоступен
+ {controller.error}
+
+
+ ) : items.length === 0 ? (
+
+
+ Сохранённых сессий пока нет
+ После завершения записи источник появится здесь без создания демонстрационных данных.
+
+ ) : selectedSession ? (
+
+
+
+
+
+
- Идентификатор
+ {selectedSession.source.id}
+
+ - Начало
- {formatTimestamp(selectedSession.source.startedAtUtc)}
+ - Длительность
- {formatDuration(selectedSession.source.durationSeconds)}
+
+
- Чтение
+ - {selectedSession.source.replayable ? "Доступна" : "Не подготовлена"}
+
+
+
+ {selectedSession.source.modalities.length > 0
+ ? selectedSession.source.modalities.map((modality) => (
+
+ {modalityLabel[modality] ?? modality}
+
+ ))
+ : Каналы не зафиксированы}
+
+
+
+
+
+ {selectedSession.evidence.length > 0 ? (
+
+ {presentedEvidence.map((evidence) => (
+ -
+
+
+ {evidence.lab.labId}
+ {evidence.label}
+
+
+ - Тип результата
- {evidence.lab.resultKind}
+
+
- Result ID
+ {evidence.lab.resultId}
+
+ - Опубликован
- {formatTimestamp(evidence.publishedAtUtc)}
+
+
+
+ ))}
+
+ ) : (
+
+
+
Связанных результатов нет
+
+ Наличие исходной записи само по себе не является выводом о качестве
+ компьютерного зрения или безопасности прохождения.
+
+
+ )}
+ {selectedSession.evidence.length > presentedEvidence.length ? (
+
+ Показаны {presentedEvidence.length} последних из {selectedSession.evidence.length}
+ {" "}связанных результатов. Полный архив остаётся в legacy LAB.
+
+ ) : null}
+
+
+ ) : null}
+
+ {(controller.catalog?.unresolvedEvidence.length ?? 0) > 0 ? (
+
+ Вне среза
+
+ {controller.catalog?.unresolvedEvidence.length} результатов ссылаются на источники
+ вне текущего загруженного среза и не приписаны к квалификационным доказательствам.
+
+
+ ) : null}
+
+ );
+}
diff --git a/apps/control-station/test/applicationArchitecture.test.mjs b/apps/control-station/test/applicationArchitecture.test.mjs
index 882729a..c8bfb92 100644
--- a/apps/control-station/test/applicationArchitecture.test.mjs
+++ b/apps/control-station/test/applicationArchitecture.test.mjs
@@ -104,3 +104,27 @@ test("laboratory UI is a bounded feature slice, not a central workspace branch",
assert.doesNotMatch(laboratoryCss, /\.e30-human-review/);
assert.match(e30HumanReviewCss, /\.e30-human-review/);
});
+
+test("Observatory is a bounded read-only slice outside legacy LAB and viewer lifecycles", async () => {
+ const workspaceHub = await read("workspaces/Workspaces.tsx");
+ const observatory = await read("workspaces/observatory/ObservatoryWorkspace.tsx");
+ const observatoryCore = await read("core/observatory/catalog.ts");
+ const workspaceCss = await read("styles/workspaces.css");
+ const observatoryCss = await read("styles/observatory.css");
+
+ assert.match(
+ workspaceHub,
+ /case "observatory":[\s\S]*