release 1.15.2: compact warn/error filter — no black voids

Omit blank log lines from mon HTML. Filter mode collapses non-matches
to zero size, uses tighter useful context, and a minimal cluster gap.
This commit is contained in:
Hernâni Marques 2026-07-19 04:43:49 +02:00
parent 66fa471d00
commit bf3a2e34dc
No known key found for this signature in database
GPG key ID: CB5738652768F7E9
3 changed files with 94 additions and 27 deletions

View file

@ -1 +1 @@
1.15.1 1.15.2

View file

@ -17,6 +17,7 @@ Git tags: `vMAJOR.FEATURE.FIX` (e.g. `v1.8.0`). File `VERSION` omits the `v` pre
| Tag | Date (UTC) | Notes | | Tag | Date (UTC) | Notes |
|-----|------------|--------| |-----|------------|--------|
| **v1.15.2** | 2026-07-19 | **Bugfix:** warn/error filter — drop blank log lines from HTML; compact console (no black voids above/mid/below); context skips SUMMARY chrome & pure box frames; tighter filter-gap. |
| **v1.15.1** | 2026-07-19 | **Bugfix:** mon HTML — phase `section()` boxes no longer painted as SUMMARY (were blue/green `sum-header`); only real SUMMARY rows get solid severity backgrounds (OK/WARN/ERROR/INFO). | | **v1.15.1** | 2026-07-19 | **Bugfix:** mon HTML — phase `section()` boxes no longer painted as SUMMARY (were blue/green `sum-header`); only real SUMMARY rows get solid severity backgrounds (OK/WARN/ERROR/INFO). |
| **v1.15.0** | 2026-07-19 | **Feature:** CHF **fake-incoming amount ladder** in `devtesting` (`DEVTESTING_LADDER=1`, default **23** log-spaced rungs **CHF:0.01 → CHF:50000**); synthetic geniban debit funds each rung; baseline plumbing probe kept. | | **v1.15.0** | 2026-07-19 | **Feature:** CHF **fake-incoming amount ladder** in `devtesting` (`DEVTESTING_LADDER=1`, default **23** log-spaced rungs **CHF:0.01 → CHF:50000**); synthetic geniban debit funds each rung; baseline plumbing probe kept. |
| **v1.14.0** | 2026-07-19 | **Feature:** SUMMARY box rows are **colour-distinct** (per-severity badge + tinted background + bold counts; verdict-coloured header); HTML mon pages style `sum-ok` / `sum-warn` / `sum-err` / … panels the same way. | | **v1.14.0** | 2026-07-19 | **Feature:** SUMMARY box rows are **colour-distinct** (per-severity badge + tinted background + bold counts; verdict-coloured header); HTML mon pages style `sum-ok` / `sum-warn` / `sum-err` / … panels the same way. |

View file

@ -549,6 +549,10 @@ def slug_error(i: int, text: str) -> str:
def render_line(ln: str, err_slugs: dict[str, str], extra_ids: list[str] | None = None) -> str: def render_line(ln: str, err_slugs: dict[str, str], extra_ids: list[str] | None = None) -> str:
# Drop blank log lines entirely — they only create black voids in the console
# (especially visible under warn/error filter). Anchors still attach to next real line.
if not (ln or "").strip():
return ""
kind = classify(ln) kind = classify(ln)
esc = html.escape(ln) esc = html.escape(ln)
for tid, slug in err_slugs.items(): for tid, slug in err_slugs.items():
@ -563,8 +567,11 @@ def render_line(ln: str, err_slugs: dict[str, str], extra_ids: list[str] | None
# HTML allows one id — join carefully # HTML allows one id — join carefully
id_attr = f' id="{html.escape(extra_ids[0])}"' id_attr = f' id="{html.escape(extra_ids[0])}"'
if len(extra_ids) > 1: if len(extra_ids) > 1:
# secondary anchors as empty spans prepended # secondary anchors as empty spans prepended (inline, zero layout impact)
spans = "".join(f'<span id="{html.escape(x)}"></span>' for x in extra_ids[1:]) spans = "".join(
f'<span id="{html.escape(x)}" class="anchor-only"></span>'
for x in extra_ids[1:]
)
return f'{spans}<div{id_attr} class="line {kind}">{esc}</div>\n' return f'{spans}<div{id_attr} class="line {kind}">{esc}</div>\n'
return f'<div{id_attr} class="line {kind}">{esc}</div>\n' return f'<div{id_attr} class="line {kind}">{esc}</div>\n'
@ -1175,38 +1182,83 @@ body.filter-warn .filter-chip { border-color: #a68b2d; background: #1a1608; colo
padding: 1px 8px; padding: 1px 8px;
} }
.filter-clear:hover { border-color: #aaa; background: #222; } .filter-clear:hover { border-color: #aaa; background: #222; }
/* Console filter: sticky + overviews stay; match lines + dimmed neighbours (v1.13.11) */ /* Console filter: sticky + overviews stay; match lines + dimmed neighbours (v1.13.11+)
body.filter-error #mon-console .line:not(.error):not(.blocker):not(.filter-ctx) { display: none !important; } v1.15.2: stamp out black voids hide blanks, zero-size non-matches, compact console */
body.filter-warn #mon-console .line:not(.warn):not(.filter-ctx) { display: none !important; } body.filter-error #mon-console,
body.filter-warn #mon-console {
padding: 6px 8px;
min-height: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
body.filter-error #mon-console .line:not(.error):not(.blocker):not(.filter-ctx),
body.filter-warn #mon-console .line:not(.warn):not(.filter-ctx) {
display: none !important;
height: 0 !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
overflow: hidden !important;
line-height: 0 !important;
}
/* empty / whitespace-only lines never take space in filter mode */
body.filter-error #mon-console .line:empty,
body.filter-warn #mon-console .line:empty,
body.filter-error #mon-console .line.line-blank,
body.filter-warn #mon-console .line.line-blank {
display: none !important;
}
/* Context lines: grayed / muted so the warn|error still stands out */ /* Context lines: grayed / muted so the warn|error still stands out */
body.filter-error #mon-console .line.filter-ctx, body.filter-error #mon-console .line.filter-ctx,
body.filter-warn #mon-console .line.filter-ctx { body.filter-warn #mon-console .line.filter-ctx {
opacity: 0.48; opacity: 0.55;
color: #7a7a7a !important; color: #8a8a8a !important;
filter: grayscale(0.9); filter: grayscale(0.85);
margin: 0 !important;
padding: 1px 4px !important;
border: none !important;
background: transparent !important;
} }
body.filter-error #mon-console .line.filter-ctx a.jump, body.filter-error #mon-console .line.filter-ctx a.jump,
body.filter-warn #mon-console .line.filter-ctx a.jump { body.filter-warn #mon-console .line.filter-ctx a.jump {
color: #6a6a6a !important; color: #6a6a6a !important;
} }
/* Visual break when filter jumps across a gap in the log */ /* Match lines stay tight */
body.filter-error #mon-console .line.error,
body.filter-error #mon-console .line.blocker,
body.filter-warn #mon-console .line.warn {
margin: 1px 0 !important;
padding: 3px 6px !important;
}
/* Compact ellipsis between distant clusters (not a black slab) */
body.filter-error #mon-console .filter-gap, body.filter-error #mon-console .filter-gap,
body.filter-warn #mon-console .filter-gap { body.filter-warn #mon-console .filter-gap {
display: block; display: block;
color: #555; color: #666;
text-align: center; text-align: center;
padding: 6px 0; padding: 0;
margin: 4px 0; margin: 2px 0;
letter-spacing: 0.35em; letter-spacing: 0.25em;
font-size: 11px; font-size: 10px;
line-height: 1.2;
user-select: none; user-select: none;
border-top: 1px dashed #333; border: none;
border-bottom: 1px dashed #333; background: transparent;
height: auto;
} }
body:not(.filter-error):not(.filter-warn) #mon-console .filter-gap { display: none !important; } body:not(.filter-error):not(.filter-warn) #mon-console .filter-gap { display: none !important; }
body.filter-error .env-context, body.filter-error .env-context,
body.filter-warn .env-context { display: none !important; } body.filter-warn .env-context { display: none !important; }
body.filter-warn .err-top { display: none; } body.filter-warn .err-top { display: none; }
/* secondary jump anchors: never affect layout */
#mon-console .anchor-only {
display: inline;
width: 0;
height: 0;
overflow: hidden;
position: absolute;
}
/* v1.12.0: collapsible monitoring env context (default closed) */ /* v1.12.0: collapsible monitoring env context (default closed) */
.env-context { .env-context {
margin: 0 0 12px; margin: 0 0 12px;
@ -1436,14 +1488,14 @@ STICKY_JS = """
} }
// v1.11.0: click error/warn stats filter log; sticky + overviews stay // v1.11.0: click error/warn stats filter log; sticky + overviews stay
// v1.13.11: keep a few neighbour lines (dimmed) so phase/group context stays readable // v1.13.11: dimmed neighbour context; v1.15.2: stamp out black voids, compact layout
var bar = document.getElementById("status-bar"); var bar = document.getElementById("status-bar");
var chip = document.getElementById("filter-chip"); var chip = document.getElementById("filter-chip");
var chipLabel = document.getElementById("filter-chip-label"); var chipLabel = document.getElementById("filter-chip-label");
var clearBtn = document.getElementById("filter-clear"); var clearBtn = document.getElementById("filter-clear");
var consoleEl = document.getElementById("mon-console"); var consoleEl = document.getElementById("mon-console");
// lines above + below each match (same count either side) // useful neighbours each side (skip blanks / SUMMARY chrome / pure box frames)
var FILTER_CTX = 3; var FILTER_CTX = 2;
function currentFilter() { function currentFilter() {
if (document.body.classList.contains("filter-error")) return "error"; if (document.body.classList.contains("filter-error")) return "error";
@ -1459,11 +1511,13 @@ STICKY_JS = """
consoleEl.querySelectorAll(".filter-gap").forEach(function (el) { consoleEl.querySelectorAll(".filter-gap").forEach(function (el) {
el.parentNode && el.parentNode.removeChild(el); el.parentNode && el.parentNode.removeChild(el);
}); });
document.body.classList.remove("filter-compact");
} }
function markFilterContext(mode) { function markFilterContext(mode) {
clearFilterContext(); clearFilterContext();
if (!consoleEl || !mode) return; if (!consoleEl || !mode) return;
document.body.classList.add("filter-compact");
var lines = Array.prototype.slice.call(consoleEl.querySelectorAll(".line")); var lines = Array.prototype.slice.call(consoleEl.querySelectorAll(".line"));
if (!lines.length) return; if (!lines.length) return;
function isMatch(el) { function isMatch(el) {
@ -1477,19 +1531,30 @@ STICKY_JS = """
var t = (el.textContent || "").replace(/\u00a0/g, " ").trim(); var t = (el.textContent || "").replace(/\u00a0/g, " ").trim();
return t.length === 0; return t.length === 0;
} }
// 0 = hide, 1 = match, 2 = context function isLowValue(el) {
if (isBlankLine(el)) return true;
if (el.classList.contains("sum-header") || el.classList.contains("sum-ok")
|| el.classList.contains("sum-warn") || el.classList.contains("sum-err")
|| el.classList.contains("sum-info") || el.classList.contains("sum-block")
|| el.classList.contains("sum-totals") || el.classList.contains("sum-verdict-ok")
|| el.classList.contains("sum-verdict-warn") || el.classList.contains("sum-verdict-err")) {
return true;
}
var t = (el.textContent || "").replace(/\u00a0/g, " ").trim();
if (/^[\-\s]+$/.test(t)) return true;
return false;
}
var keep = new Array(lines.length); var keep = new Array(lines.length);
var i, j, k, n, idx; var i, j, k, n, idx;
for (i = 0; i < lines.length; i++) keep[i] = 0; for (i = 0; i < lines.length; i++) keep[i] = 0;
for (i = 0; i < lines.length; i++) { for (i = 0; i < lines.length; i++) {
if (!isMatch(lines[i])) continue; if (!isMatch(lines[i])) continue;
keep[i] = 1; keep[i] = 1;
// ±FILTER_CTX *non-empty* neighbours (skip blank lines so filter is not full of voids)
n = 0; n = 0;
idx = i - 1; idx = i - 1;
while (idx >= 0 && n < FILTER_CTX) { while (idx >= 0 && n < FILTER_CTX) {
if (keep[idx] === 1) { idx--; continue; } if (keep[idx] === 1) { idx--; continue; }
if (isBlankLine(lines[idx])) { idx--; continue; } if (isLowValue(lines[idx])) { idx--; continue; }
keep[idx] = 2; keep[idx] = 2;
n++; n++;
idx--; idx--;
@ -1498,7 +1563,7 @@ STICKY_JS = """
idx = i + 1; idx = i + 1;
while (idx < lines.length && n < FILTER_CTX) { while (idx < lines.length && n < FILTER_CTX) {
if (keep[idx] === 1) { idx++; continue; } if (keep[idx] === 1) { idx++; continue; }
if (isBlankLine(lines[idx])) { idx++; continue; } if (isLowValue(lines[idx])) { idx++; continue; }
keep[idx] = 2; keep[idx] = 2;
n++; n++;
idx++; idx++;
@ -1506,10 +1571,11 @@ STICKY_JS = """
} }
for (j = 0; j < lines.length; j++) { for (j = 0; j < lines.length; j++) {
if (keep[j] === 2) lines[j].classList.add("filter-ctx"); if (keep[j] === 2) lines[j].classList.add("filter-ctx");
// never show pure blank lines in filter mode if (isBlankLine(lines[j])) {
if (isBlankLine(lines[j]) && keep[j] !== 1) keep[j] = 0; keep[j] = 0;
lines[j].classList.add("line-blank");
}
} }
// dashed gap between non-contiguous visible clusters
var prevVisible = -2; var prevVisible = -2;
for (k = 0; k < lines.length; k++) { for (k = 0; k < lines.length; k++) {
if (keep[k] === 0) continue; if (keep[k] === 0) continue;