diff --git a/plugins/xgrids-k1/frontend/src/components/K1ProvisioningPipeline.tsx b/plugins/xgrids-k1/frontend/src/components/K1ProvisioningPipeline.tsx
index 0282d91..3453a6d 100644
--- a/plugins/xgrids-k1/frontend/src/components/K1ProvisioningPipeline.tsx
+++ b/plugins/xgrids-k1/frontend/src/components/K1ProvisioningPipeline.tsx
@@ -31,6 +31,7 @@ import type {
} from "../api";
import { profileSelectionForConnectionMode } from "../compatibility";
import {
+ BLE_DISCOVERY_TIMEOUT_SECONDS,
DEFAULT_CONNECTION_MODE,
connectionModeOptions,
type ConnectionMode,
@@ -66,10 +67,12 @@ import {
type ReadOnlyConnectionObservationTarget,
} from "../lifecycle";
import { finiteMetric } from "../presentation";
+import { STATION_WIFI_FAILURE_MESSAGES } from "../networkFailurePresentation";
import type { XgridsK1Controller } from "../runtimeContext";
import { K1OperatorError } from "./K1OperatorError";
import {
connectionActionAuthoritySnapshot,
+ networkProvisionFailureMessage,
type BleDiscoverySubmitResult,
type ConnectionActionAuthoritySnapshot,
} from "../useXgridsK1Runtime";
@@ -1866,7 +1869,7 @@ export function K1ProvisioningPipeline({
const connectionRecoveryRequired = connectionRecoveryIsRequired(
connectionRecoveryKey,
escapedConnectionRecoveryKey,
- ) && !(searchRequested && !searchDisplayActive);
+ ) && (connectionAttemptFailed || !(searchRequested && !searchDisplayActive));
const presentedConnectionRecoveryRequired = !livePreparationPending && (
connectionRecoveryRequired || connectionRecoveryVerificationPending
);
@@ -2089,7 +2092,7 @@ export function K1ProvisioningPipeline({
const startedAt = Date.now();
const updateCountdown = () => {
const elapsedSeconds = Math.floor((Date.now() - startedAt) / 1_000);
- setScanSecondsRemaining(Math.max(0, 6 - elapsedSeconds));
+ setScanSecondsRemaining(Math.max(0, BLE_DISCOVERY_TIMEOUT_SECONDS - elapsedSeconds));
};
updateCountdown();
const timer = window.setInterval(updateCountdown, 250);
@@ -2350,7 +2353,7 @@ export function K1ProvisioningPipeline({
let scanResult: BleDiscoverySubmitResult | null = null;
try {
if (!runtimeActionIsCurrent(actionFence)) return;
- scanResult = await scanWithResult({ durationSeconds: 6 });
+ scanResult = await scanWithResult({ durationSeconds: BLE_DISCOVERY_TIMEOUT_SECONDS });
if (
!localScenarioActionEpochIsCurrent(
actionEpoch,
@@ -2573,12 +2576,12 @@ export function K1ProvisioningPipeline({
}
const freshStartAllowed = result.intentDisposition === "release";
- const failureMessage = freshStartAllowed
+ const failureMessage = networkProvisionFailureMessage(observedOperation) ?? (freshStartAllowed
&& provisioningFailureRequiresFreshCandidate(result.failureReasonCode)
? "Результат Bluetooth-поиска устарел до отправки. Настройки не отправлены; выполните явный повторный поиск."
: freshStartAllowed
? "Подключение не началось. Настройки удалены; после устранения причины начните новый явный поиск."
- : "Результат отправки неизвестен. Повторное применение заблокировано, чтобы не отправить BLE-команду дважды; дождитесь, пока система определит безопасное продолжение.";
+ : "Подключение не подтверждено. Проверьте состояние K1 или начните новое подключение; автоматического повтора не было.");
setConnectionAttemptPresentation((current) => (
current?.idempotencyKey === idempotencyKey
? {
@@ -3203,7 +3206,9 @@ export function K1ProvisioningPipeline({
disabled={isBusy || modeResetInFlight}
onClick={() => void changeDesiredConnectionMode(connectionMode)}
>
- {savedBridgeNetworkSetupRequired
+ {STATION_WIFI_FAILURE_MESSAGES[connectionRecoveryAttempt?.public_error_code ?? ""]
+ ? "Указать сеть Wi‑Fi заново"
+ : savedBridgeNetworkSetupRequired
? "Подключить K1 к общей сети"
: "Подключить новый K1"}
@@ -3256,7 +3261,7 @@ export function K1ProvisioningPipeline({
title="Подключение"
status={
searchActive
- ? `Поиск · ${scanSecondsRemaining ?? 6} с`
+ ? `Поиск · до ${scanSecondsRemaining ?? BLE_DISCOVERY_TIMEOUT_SECONDS} с`
: connectionAttemptSettling
? "Устройство выбрано"
: presentedPhysicalRecoveryRequired
@@ -3316,7 +3321,7 @@ export function K1ProvisioningPipeline({
>
- Поиск Bluetooth · {scanSecondsRemaining ?? 6} с
+ Поиск Bluetooth · до {scanSecondsRemaining ?? BLE_DISCOVERY_TIMEOUT_SECONDS} с
) : networkRecoveryRequired
diff --git a/plugins/xgrids-k1/frontend/src/configuration.ts b/plugins/xgrids-k1/frontend/src/configuration.ts
index 6cbb679..a51f272 100644
--- a/plugins/xgrids-k1/frontend/src/configuration.ts
+++ b/plugins/xgrids-k1/frontend/src/configuration.ts
@@ -4,6 +4,9 @@ export type ConnectionMode = "bridge" | "quick-connect" | "direct-connect";
export type MountType = "handheld" | "vehicle-mounted" | "uav" | "backpack";
export type GnssMode = "none" | "rtk" | "ppk";
+// Upper bound for one continuous discovery session; it can finish earlier when K1 is visible.
+export const BLE_DISCOVERY_TIMEOUT_SECONDS = 20;
+
export const DEFAULT_CONNECTION_MODE = "bridge" as const satisfies ConnectionMode;
export const SUPPORTED_MOUNT_TYPE = "handheld" as const satisfies MountType;
export const SUPPORTED_GNSS_MODE = "none" as const satisfies GnssMode;
diff --git a/plugins/xgrids-k1/frontend/src/connectionAttempt.ts b/plugins/xgrids-k1/frontend/src/connectionAttempt.ts
new file mode 100644
index 0000000..4f12332
--- /dev/null
+++ b/plugins/xgrids-k1/frontend/src/connectionAttempt.ts
@@ -0,0 +1,17 @@
+/** Public read model. Journal and current backend policy remain authoritative. */
+export interface XgridsConnectionAttemptSummary {
+ schema_version:'missioncore.xgrids-k1-connection-attempt/v1';
+ attempt_id:string;
+ connection_mode:'bridge'|'quick-connect'|'direct-connect';
+ status:'accepted'|'running'|'operator_action_required'|'succeeded'|'failed'|'cancelled'|'timed_out'|'interrupted';
+ phase:'network_applied'|'network_not_applied'|'network_outcome_unknown';
+ control_state:'ready'|'control_not_ready'|'unknown';
+ stage:string;
+ public_error_code:string|null;
+ side_effect_status:string;
+ safe_next_action:'wait-for-current-attempt'|'continue-with-control-verification'|'verify-control-read-only'|'start-acquisition'|'stop-acquisition'|'stop-local-receiver'|'retire-unavailable-physical-target'|'scan-select-connect'|'manual-recovery-required';
+ automatic_retry:false;
+ accepted_at:string|null;
+ completed_at:string|null;
+ recovery_operation_id?:string|null;
+}
diff --git a/plugins/xgrids-k1/frontend/src/networkFailurePresentation.ts b/plugins/xgrids-k1/frontend/src/networkFailurePresentation.ts
new file mode 100644
index 0000000..b3016d2
--- /dev/null
+++ b/plugins/xgrids-k1/frontend/src/networkFailurePresentation.ts
@@ -0,0 +1,7 @@
+/** Reviewed K1 station replies; Quick Connect host errors have separate copy. */
+export const STATION_WIFI_FAILURE_MESSAGES: Readonly