| @@ -281,6 +281,8 @@ Einführen eines klaren Betriebsmodells mit Fault-, Recovery- und Muted-Zuständ | |||
| - Control-plane now exposes `POST /runtime/fault/reset` so operators can acknowledge `faulted` state; `TestRuntimeFaultReset*` covers the new HTTP path. | |||
| - Control-plane UI now also offers a Danger Zone `Reset Fault` button that calls the same endpoint so operators can acknowledge faults from the dashboard. | |||
| - Control-plane UI now posts an ops toast/log entry whenever the runtime state shifts so escalations and manual acknowledgements are immediately visible. | |||
| ## Zielzustände laut Konzept | |||
| - `idle` | |||
| @@ -331,12 +333,14 @@ Einführen eines klaren Betriebsmodells mit Fault-, Recovery- und Muted-Zuständ | |||
| | 2026-04-05 | Faulted escalation on persistent critical queue | `muted` now surfaces `RuntimeStateFaulted` when queue health stays critical and metrics capture every transition. | | |||
| | 2026-04-05 | Manual fault reset endpoint | Added `POST /runtime/fault/reset` so operators can acknowledge `faulted` before the supervisor re-enters recovery. | | |||
| | 2026-04-05 | Fault-reset UI shortcut | Danger Zone now hosts a Reset Fault button wired to `/runtime/fault/reset` so operators get an in-app acknowledgement path without manual HTTP calls. | | |||
| | 2026-04-06 | Runtime transition visibility cue | Control UI now posts toast/log entries for runtime state shifts so ops instantly sees escalations and manual reset acknowledgements. | | |||
| ## WS-02 Verifikation | |||
| | Datum | Fokus | Ergebnis | | |||
| |---|---|---| | |||
| | 2026-04-05 | Faulted path + transition counters | `go test ./...` exercises `TestEngineFaultsAfterMutedCriticalStreak` and `TestRuntimeTransitionCounters`, while `/runtime` now surfaces `engine.degradedTransitions`, `engine.mutedTransitions`, `engine.faultedTransitions`, `engine.faultCount`, and the last fault via `txBridge`. | | |||
| | 2026-04-05 | Runtime fault reset API | `go test ./...` now runs `TestRuntimeFaultReset*`, verifying the new HTTP path and controller error scenarios. | | |||
| | 2026-04-06 | Runtime transition visibility | ✅ `go test ./...`; manual UI smoke verification still pending to ensure the toast/log flow shows every runtime shift. | | |||
| --- | |||
| @@ -1163,6 +1163,7 @@ const state = { | |||
| configOk: false, | |||
| runtimeOk: false, | |||
| }, | |||
| lastRuntimeState: '', | |||
| draft: { | |||
| frequencyMHz: undefined, | |||
| ps: undefined, | |||
| @@ -1338,6 +1339,7 @@ async function loadRuntime({ silent = true } = {}) { | |||
| state.server.runtime = runtime; | |||
| state.server.runtimeOk = true; | |||
| state.server.lastRuntimeAt = nowTs(); | |||
| notifyRuntimeTransition(runtime.engine); | |||
| pushHistory(runtime); | |||
| setConnection(true, state.pendingRequests > 0 ? 'busy' : 'connected'); | |||
| render(); | |||
| @@ -1785,6 +1787,41 @@ function runtimeStateClass(engineState) { | |||
| } | |||
| } | |||
| function normalizeRuntimeState(stateName) { | |||
| const normalized = (typeof stateName === 'string' ? stateName.trim().toLowerCase() : ''); | |||
| return normalized || 'idle'; | |||
| } | |||
| function runtimeStateSeverity(stateName) { | |||
| const normalized = normalizeRuntimeState(stateName); | |||
| switch (normalized) { | |||
| case 'running': | |||
| return 'ok'; | |||
| case 'degraded': | |||
| case 'muted': | |||
| return 'warn'; | |||
| case 'faulted': | |||
| return 'err'; | |||
| default: | |||
| return 'info'; | |||
| } | |||
| } | |||
| function notifyRuntimeTransition(engine) { | |||
| if (!engine) return; | |||
| const next = normalizeRuntimeState(engine.state); | |||
| const prev = state.lastRuntimeState; | |||
| state.lastRuntimeState = next; | |||
| if (!prev || prev === next) return; | |||
| const message = `Runtime ${prev.toUpperCase()} → ${next.toUpperCase()}`; | |||
| const severity = runtimeStateSeverity(next); | |||
| const logLevel = severity === 'err' ? 'err' : (severity === 'warn' ? 'warn' : 'info'); | |||
| toast(message, severity); | |||
| log(message, logLevel); | |||
| } | |||
| function updateHealth(engine, audioStream) { | |||
| engine = engine || {}; | |||
| updateText('health-http', state.server.configOk ? 'OK' : 'OFFLINE'); | |||