Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ebc9fc27c | ||
|
|
99eafaf02d |
@@ -123,9 +123,12 @@ export function FloatingObservationWindow({
|
||||
return () => observer.disconnect();
|
||||
}, [boundsRef]);
|
||||
|
||||
const initialRect = bounds ? initialObservationWindowRect(index, count, bounds) : null;
|
||||
// Camera media admission must not wait for the viewport observer. A source
|
||||
// can arrive in the same commit as a workspace transition; mounting it with
|
||||
// the canonical fallback geometry keeps the browser transport alive until
|
||||
// the real bounds are measured immediately afterward.
|
||||
const initialRect = initialObservationWindowRect(index, count, bounds ?? undefined);
|
||||
const windowRect = rect ?? initialRect;
|
||||
if (!windowRect) return null;
|
||||
|
||||
return (
|
||||
<WorkspaceWindow
|
||||
|
||||
@@ -6788,6 +6788,38 @@ test("read-only reconnect keeps its card and loader across a transient projectio
|
||||
)),
|
||||
]);
|
||||
|
||||
const runningVerificationState = structuredClone(initialState);
|
||||
runningVerificationState.connection_attempt = {
|
||||
schema_version: "missioncore.xgrids-k1-connection-attempt/v1",
|
||||
attempt_id: "verify-saved-k1-running",
|
||||
connection_mode: "bridge",
|
||||
status: "running",
|
||||
phase: "network_applied",
|
||||
control_state: "unknown",
|
||||
stage: "host-route-and-control-endpoint",
|
||||
public_error_code: null,
|
||||
side_effect_status: "network-applied",
|
||||
safe_next_action: "wait-for-current-attempt",
|
||||
automatic_retry: false,
|
||||
accepted_at: OBSERVED_AT,
|
||||
completed_at: null,
|
||||
timeline: [],
|
||||
};
|
||||
currentState = runningVerificationState;
|
||||
tree = harness.render(props());
|
||||
const runningMarkup = renderToStaticMarkup(tree);
|
||||
const runningReconnect = buttonMarkupWithText(
|
||||
runningMarkup,
|
||||
"Переподключение…",
|
||||
);
|
||||
assert.equal(runningReconnect.length, 1);
|
||||
assert.match(runningReconnect[0], /disabled/);
|
||||
assert.match(runningReconnect[0], /aria-busy="true"/);
|
||||
assert.match(runningReconnect[0], /nodedc-activity-indicator/);
|
||||
assert.match(runningMarkup, /Сохранённое подключение/);
|
||||
assert.doesNotMatch(runningMarkup, /Команда настройки завершена/);
|
||||
assert.doesNotMatch(runningMarkup, /Подтверждение управления/);
|
||||
|
||||
const transientState = structuredClone(initialState);
|
||||
transientState.connection_policy.allowed_actions =
|
||||
transientState.connection_policy.allowed_actions.filter(
|
||||
@@ -7461,7 +7493,11 @@ test("applied-network recovery spends the old intent and admits only explicit se
|
||||
assert.match(source, /BLE-команда не повторяется/);
|
||||
assert.match(
|
||||
source,
|
||||
/status=\{\s*searchActive[\s\S]*?: presentedPhysicalRecoveryRequired[\s\S]*?: networkRecoveryRequired[\s\S]*?>\s*\{searchActive \? \([\s\S]*?Поиск Bluetooth · \{scanSecondsRemaining \?\? 6\} с[\s\S]*?: networkRecoveryRequired && !connectionAttemptSettling \? \(/,
|
||||
/status=\{\s*searchActive[\s\S]*?: presentedPhysicalRecoveryRequired[\s\S]*?: presentedConnectionRecoveryRequired[\s\S]*?: networkRecoveryRequired/,
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/networkRecoveryRequired\s*&& !connectionAttemptSettling\s*&& !connectionRecoveryVerificationPending \? \(/,
|
||||
);
|
||||
|
||||
assert.match(
|
||||
|
||||
@@ -29,6 +29,7 @@ let cameraTransportCallbackIsCurrent;
|
||||
let cameraBrowserTransportIdentity;
|
||||
let liveRerunRecoveryAuthorityIdentity;
|
||||
let initialObservationWindowRect;
|
||||
let FloatingObservationWindow;
|
||||
let ObservationTimeline;
|
||||
let shouldCaptureWorkspacePointer;
|
||||
let normalizeTimelineRange;
|
||||
@@ -86,9 +87,11 @@ before(async () => {
|
||||
} = await server.ssrLoadModule(
|
||||
"/src/core/observation/liveCameraRecovery.ts",
|
||||
));
|
||||
({ initialObservationWindowRect, shouldCaptureWorkspacePointer } = await server.ssrLoadModule(
|
||||
"/src/components/FloatingObservationWindow.tsx",
|
||||
));
|
||||
({
|
||||
FloatingObservationWindow,
|
||||
initialObservationWindowRect,
|
||||
shouldCaptureWorkspacePointer,
|
||||
} = await server.ssrLoadModule("/src/components/FloatingObservationWindow.tsx"));
|
||||
({ liveRerunRecoveryAuthorityIdentity } = await server.ssrLoadModule(
|
||||
"/src/core/observation/liveReceiverWatchdog.ts",
|
||||
));
|
||||
@@ -133,6 +136,59 @@ test("observation camera windows tile from the bottom-right above the live timel
|
||||
assert.ok(right.y + right.height <= bounds.height - 64);
|
||||
});
|
||||
|
||||
test("live camera window keeps its player mounted before viewport measurement", () => {
|
||||
const source = {
|
||||
id: "k1:sensor.camera.right",
|
||||
sourceId: "sensor.camera.right",
|
||||
label: "Правая камера",
|
||||
description: "Live camera",
|
||||
modality: "video",
|
||||
availability: "streaming",
|
||||
transport: "websocket",
|
||||
endpointLabel: "Локальный видеопоток",
|
||||
previewUrl: null,
|
||||
delivery: {
|
||||
id: "camera-preview-4",
|
||||
kind: "mse-fmp4-websocket",
|
||||
url: "/camera-preview/4",
|
||||
mediaType: 'video/mp4; codecs="avc1.641028"',
|
||||
},
|
||||
activation: {
|
||||
groupId: "k1:device-session:camera.preview.decoder",
|
||||
maxActive: 1,
|
||||
selected: true,
|
||||
controllable: false,
|
||||
},
|
||||
binding: {
|
||||
deviceId: "k1-a",
|
||||
deviceSessionId: "device-session",
|
||||
acquisitionId: "acquisition-quick",
|
||||
},
|
||||
capabilities: {
|
||||
defaultVisible: true,
|
||||
overlay: true,
|
||||
resizable: true,
|
||||
timelineMode: "live-only",
|
||||
},
|
||||
};
|
||||
const noop = () => undefined;
|
||||
const markup = renderToStaticMarkup(createElement(FloatingObservationWindow, {
|
||||
source,
|
||||
index: 0,
|
||||
count: 1,
|
||||
boundsRef: { current: null },
|
||||
maximized: false,
|
||||
active: true,
|
||||
onRectChange: noop,
|
||||
onMaximizedChange: noop,
|
||||
onActivate: noop,
|
||||
onClose: noop,
|
||||
}));
|
||||
|
||||
assert.match(markup, /<video/);
|
||||
assert.match(markup, /Правая камера/);
|
||||
});
|
||||
|
||||
test("observation camera windows stack without overlap when the viewport is narrow", () => {
|
||||
const bounds = { width: 420, height: 700 };
|
||||
const lower = initialObservationWindowRect(0, 2, bounds);
|
||||
|
||||
@@ -3265,16 +3265,16 @@ export function K1ProvisioningPipeline({
|
||||
: savedBridgeNetworkSetupRequired
|
||||
? "Нужна настройка сети"
|
||||
: "Нужно выбрать действие"
|
||||
: networkRecoveryRequired
|
||||
? appliedControlSettlementPending
|
||||
? "Подтверждение управления"
|
||||
: "Управление не подтверждено · выбор заблокирован"
|
||||
: presentedConnectionRecoveryRequired
|
||||
? connectionReconnectPending
|
||||
? "Переподключение…"
|
||||
: connectionRecoveryTarget
|
||||
? "Можно переподключиться"
|
||||
: "Подключить заново"
|
||||
: networkRecoveryRequired
|
||||
? appliedControlSettlementPending
|
||||
? "Подтверждение управления"
|
||||
: "Управление не подтверждено · выбор заблокирован"
|
||||
: connectionEstablished
|
||||
? "Подключение установлено"
|
||||
: explicitProvisioningDraftStale
|
||||
@@ -3319,7 +3319,9 @@ export function K1ProvisioningPipeline({
|
||||
Поиск Bluetooth · {scanSecondsRemaining ?? 6} с
|
||||
</strong>
|
||||
</div>
|
||||
) : networkRecoveryRequired && !connectionAttemptSettling ? (
|
||||
) : networkRecoveryRequired
|
||||
&& !connectionAttemptSettling
|
||||
&& !connectionRecoveryVerificationPending ? (
|
||||
<div className="connection-summary" role="status">
|
||||
<span>
|
||||
{appliedControlSettlementPending
|
||||
|
||||
Reference in New Issue
Block a user