diff --git a/docs/pro-runtime-hardening-workboard.md b/docs/pro-runtime-hardening-workboard.md
index 8fb6a52..e0f5f3b 100644
--- a/docs/pro-runtime-hardening-workboard.md
+++ b/docs/pro-runtime-hardening-workboard.md
@@ -382,10 +382,14 @@ Vollständige Sichtbarkeit auf Runtime, Queue, Writer, Generator, RF-Selbsttests
- `rf_selftest_rds_57k_db`
## WS-04 Entscheidungslog
-- Noch leer
+| Datum | Entscheidung | Notiz |
+| --- | --- | --- |
+| 2026-04-06 | High-watermark trend sparkline | Captured audio high-watermark duration history and surface it as a new Health-panel sparkline for queue pressure visibility. |
## WS-04 Verifikation
-- Noch leer
+| Datum | Fokus | Ergebnis |
+| --- | --- | --- |
+| 2026-04-06 | High-watermark trend sparkline | `go test ./...` plus manual UI check confirm the new sparkline updates with runtime audio stats. |
---
diff --git a/internal/control/ui.html b/internal/control/ui.html
index e1a1eaa..8764bff 100644
--- a/internal/control/ui.html
+++ b/internal/control/ui.html
@@ -770,6 +770,16 @@ input.input-error {
.health-line .val.good { color: var(--green); }
.health-line .val.warn { color: var(--amber); }
.health-line .val.err { color: var(--accent); }
+.health-trend {
+ margin-top: 10px;
+}
+.health-trend-label {
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: var(--text-muted);
+ margin-bottom: 6px;
+}
.fault-history {
margin-top: 12px;
@@ -1170,6 +1180,10 @@ input.input-error {
+
+
High Watermark Trend
+
+
@@ -1304,6 +1318,7 @@ const state = {
audio: [],
underruns: [],
tx: [],
+ highWatermark: [],
},
runtimeTransitions: [],
freqPresetIndex: 0,
@@ -1479,6 +1494,9 @@ function pushHistory(runtime) {
const driver = runtime.driver || {};
const audio = runtime.audioStream || {};
pushChart(state.charts.audio, typeof audio.buffered === 'number' ? audio.buffered : 0);
+ const highWatermarkDurationSeconds = Number(audio.highWatermarkDurationSeconds);
+ const normalizedHighWatermark = Number.isFinite(highWatermarkDurationSeconds) ? highWatermarkDurationSeconds : 0;
+ pushChart(state.charts.highWatermark, normalizedHighWatermark);
pushChart(state.charts.underruns, Number(engine.underruns ?? driver.underruns ?? 0));
const txState = String(engine.state || 'idle').toLowerCase();
pushChart(state.charts.tx, txState === 'running' ? 1 : state.txBusy ? 0.55 : 0.05);
@@ -1951,7 +1969,27 @@ function render() {
updateTransitionHistory();
updateResetHint(engine);
updateMeters(engine, driver, audioStream);
+ const highWatermarkDurationSecondsRaw = audioStream?.highWatermarkDurationSeconds;
+ const highWatermarkDurationSeconds = Number(highWatermarkDurationSecondsRaw);
+ const highWatermarkFramesRaw = audioStream?.highWatermark;
+ const highWatermarkFrames = Number.isFinite(Number(highWatermarkFramesRaw)) ? Number(highWatermarkFramesRaw) : 0;
+ const capacityRaw = audioStream?.capacity;
+ const capacity = Number.isFinite(Number(capacityRaw)) ? Number(capacityRaw) : 0;
+ const bufferedDurationSecondsRaw = audioStream?.bufferedDurationSeconds;
+ const bufferedDurationSeconds = Number(bufferedDurationSecondsRaw);
+ const hasBufferedDuration = Number.isFinite(bufferedDurationSeconds);
+ const hasHighWatermarkDuration = Number.isFinite(highWatermarkDurationSeconds);
+ const highWatermarkRatio = capacity > 0 ? Math.min(1, highWatermarkFrames / capacity) : 0;
+ let highWatermarkMode = 'good';
+ if (highWatermarkRatio >= 0.95) highWatermarkMode = 'err';
+ else if (highWatermarkRatio >= 0.65) highWatermarkMode = 'warn';
+ const sparkHighWatermarkMax = Math.max(
+ 1,
+ hasHighWatermarkDuration ? highWatermarkDurationSeconds : 0,
+ hasBufferedDuration ? bufferedDurationSeconds : 0
+ );
drawSparkline('spark-audio', state.charts.audio, 'good', 1);
+ drawSparkline('spark-high-watermark', state.charts.highWatermark, highWatermarkMode, sparkHighWatermarkMax);
drawSparkline('spark-underruns', state.charts.underruns, underruns > 0 ? 'err' : 'warn');
drawSparkline('spark-tx', state.charts.tx, txStateValue === 'running' ? 'good' : 'warn', 1);
applyMobilePanelDefaults();