release 1.14.0: colour-distinct SUMMARY boxes per severity

Terminal SUMMARY uses verdict-tinted header, bold counts, and
per-row background tints for OK/ERROR/WARN/INFO/BLOCK. HTML mon
pages map those lines to sum-* CSS panels with matching emphasis.
This commit is contained in:
Hernâni Marques 2026-07-19 04:32:26 +02:00
parent 31565f46d1
commit 8f3d221e02
No known key found for this signature in database
GPG key ID: CB5738652768F7E9
4 changed files with 153 additions and 43 deletions

View file

@ -1 +1 @@
1.13.13 1.14.0

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.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.13.13** | 2026-07-19 | **Bugfix:** progress estimate for **devtesting** / fake-franken is 8 (happy-path numbered checks), not 10 — avoids mid-run 42/48 then end snap 46/46. | | **v1.13.13** | 2026-07-19 | **Bugfix:** progress estimate for **devtesting** / fake-franken is 8 (happy-path numbered checks), not 10 — avoids mid-run 42/48 then end snap 46/46. |
| **v1.13.12** | 2026-07-19 | **Bugfix:** SUMMARY chrome (`[ WARN ] 2`, `warnings only`, OK/ERROR counts without `#NNN`) is **meta**, not warn/error — sticky counts + warn filter no longer double SUMMARY rows; filter context skips blank lines. | | **v1.13.12** | 2026-07-19 | **Bugfix:** SUMMARY chrome (`[ WARN ] 2`, `warnings only`, OK/ERROR counts without `#NNN`) is **meta**, not warn/error — sticky counts + warn filter no longer double SUMMARY rows; filter context skips blank lines. |
| **v1.13.11** | 2026-07-19 | **Bugfix:** sticky error/warn filter keeps **±3 dimmed context lines** around each match (gray/grayscale) so phase/group context stays readable; dashed gap between distant clusters. | | **v1.13.11** | 2026-07-19 | **Bugfix:** sticky error/warn filter keeps **±3 dimmed context lines** around each match (gray/grayscale) so phase/group context stays readable; dashed gap between distant clusters. |

100
lib.sh
View file

@ -894,56 +894,94 @@ section() {
} }
# Print SUMMARY totals box for given counts (phase-local or global). # Print SUMMARY totals box for given counts (phase-local or global).
# Each severity row is colour-distinct: badge + full-line bg tint + bold count (v1.14.0).
# $1=pass $2=fail $3=warn $4=info $5=block # $1=pass $2=fail $3=warn $4=info $5=block
_summary_totals_box() { _summary_totals_box() {
local pass_n="$1" fail_n="$2" warn_n="$3" info_n="$4" blk_n="$5" local pass_n="$1" fail_n="$2" warn_n="$3" info_n="$4" blk_n="$5"
if [ "${BOX:-0}" = "1" ]; then local hdr_bg hdr_fg _sum_lbl
printf -- '\n%s┌ %s ┐%s\n' "$BG_SEC" "$(i18n_tag SUMMARY)" "$N" _sum_lbl=$(i18n_tag SUMMARY)
_sum_badge() { # $1=bg $2=tag $3=fg $4=n # Header colour follows overall verdict
local _tg if [ "$fail_n" -gt 0 ] || [ "$blk_n" -gt 0 ]; then
_tg=$(i18n_tag "$2") hdr_bg="${BG_ERR}"
printf -- ' %s┌ %-5s┐%s %s%4d%s\n' "$1" "$_tg" "$N" "$3" "$4" "$N" hdr_fg="${R}"
} elif [ "$warn_n" -gt 0 ]; then
_sum_badge "$BG_OK" "OK" "$G" "$pass_n" hdr_bg="${BG_WARN}"
[ "$fail_n" -gt 0 ] && _sum_badge "$BG_ERR" "ERROR" "$R" "$fail_n" hdr_fg="${Y}"
[ "$warn_n" -gt 0 ] && _sum_badge "$BG_WARN" "WARN" "$Y" "$warn_n"
[ "$info_n" -gt 0 ] && _sum_badge "$BG_INFO" "INFO" "$C" "$info_n"
[ "$blk_n" -gt 0 ] && _sum_badge "$BG_BLK" "BLOCK" "$M" "$blk_n"
else else
printf -- '\n[ %s ]\n' "$(i18n_tag SUMMARY)" hdr_bg="${BG_OK}"
printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag OK)" "$G" "$pass_n" "$N" hdr_fg="${G}"
[ "$fail_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag ERROR)" "$R" "$fail_n" "$N"
[ "$warn_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag WARN)" "$Y" "$warn_n" "$N"
[ "$info_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag INFO)" "$C" "$info_n" "$N"
[ "$blk_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag BLOCK)" "$M" "$blk_n" "$N"
fi fi
# one-line rollup (bold label, severity-coloured counts)
printf -- ' %stotals:%s %s%d OK%s' "$B" "$N" "$G" "$pass_n" "$N" if [ "${BOX:-0}" = "1" ]; then
[ "$fail_n" -gt 0 ] && printf -- ' · %s%d ERROR%s' "$R" "$fail_n" "$N" # Dim full-line tints (bold fg on soft bg) — distinct per severity
[ "$warn_n" -gt 0 ] && printf -- ' · %s%d WARN%s' "$Y" "$warn_n" "$N" local ROW_OK=$'\e[1;32;48;5;22m' # bold green on dark green
[ "$info_n" -gt 0 ] && printf -- ' · %s%d INFO%s' "$C" "$info_n" "$N" local ROW_ERR=$'\e[1;97;48;5;52m' # bold white on dark red
[ "$blk_n" -gt 0 ] && printf -- ' · %s%d BLOCKER%s' "$M" "$blk_n" "$N" local ROW_WARN=$'\e[1;30;48;5;178m' # bold black on gold
local ROW_INFO=$'\e[1;97;48;5;24m' # bold white on dark blue
local ROW_BLK=$'\e[1;97;48;5;53m' # bold white on dark magenta
local ROW_TOT=$'\e[1;37;48;5;236m' # bold white on dark gray
printf -- '\n%s╔══════════════════════════════════════╗%s\n' "$hdr_bg" "$N"
printf -- '%s║ %-34s ║%s\n' "$hdr_bg" "$_sum_lbl" "$N"
printf -- '%s╚══════════════════════════════════════╝%s\n' "$hdr_bg" "$N"
# $1=row_bg $2=badge_bg $3=tag $4=n $5=label_fg — full-width-ish row
_sum_row() {
local row_bg="$1" badge="$2" tag="$3" n="$4" fg="$5" _tg pad
_tg=$(i18n_tag "$tag")
pad=$(printf '%-5s' "$_tg")
printf -- ' %s┌ %s┐%s %s %s%6d%s %s%-12s%s\n' \
"$badge" "$pad" "$N" \
"$row_bg" "$B" "$n" "$N" \
"$fg$B" "$_tg" "$N"
}
_sum_row "$ROW_OK" "$BG_OK" "OK" "$pass_n" "$G"
[ "$fail_n" -gt 0 ] && _sum_row "$ROW_ERR" "$BG_ERR" "ERROR" "$fail_n" "$R"
[ "$warn_n" -gt 0 ] && _sum_row "$ROW_WARN" "$BG_WARN" "WARN" "$warn_n" "$Y"
[ "$info_n" -gt 0 ] && _sum_row "$ROW_INFO" "$BG_INFO" "INFO" "$info_n" "$C"
[ "$blk_n" -gt 0 ] && _sum_row "$ROW_BLK" "$BG_BLK" "BLOCK" "$blk_n" "$M"
# totals rollup on tinted bar
printf -- ' %s %s totals: %s%d OK%s' "$ROW_TOT" "$B" "$G$B" "$pass_n" "$N$ROW_TOT"
[ "$fail_n" -gt 0 ] && printf -- ' %s·%s %s%d ERROR%s' "$D" "$N$ROW_TOT" "$R$B" "$fail_n" "$N$ROW_TOT"
[ "$warn_n" -gt 0 ] && printf -- ' %s·%s %s%d WARN%s' "$D" "$N$ROW_TOT" "$Y$B" "$warn_n" "$N$ROW_TOT"
[ "$info_n" -gt 0 ] && printf -- ' %s·%s %s%d INFO%s' "$D" "$N$ROW_TOT" "$C$B" "$info_n" "$N$ROW_TOT"
[ "$blk_n" -gt 0 ] && printf -- ' %s·%s %s%d BLOCKER%s' "$D" "$N$ROW_TOT" "$M$B" "$blk_n" "$N$ROW_TOT"
printf -- ' %s\n' "$N"
else
printf -- '\n[ %s ]\n' "$_sum_lbl"
printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag OK)" "$G$B" "$pass_n" "$N"
[ "$fail_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag ERROR)" "$R$B" "$fail_n" "$N"
[ "$warn_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag WARN)" "$Y$B" "$warn_n" "$N"
[ "$info_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag INFO)" "$C$B" "$info_n" "$N"
[ "$blk_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag BLOCK)" "$M$B" "$blk_n" "$N"
printf -- ' %stotals:%s %s%d OK%s' "$B" "$N" "$G$B" "$pass_n" "$N"
[ "$fail_n" -gt 0 ] && printf -- ' · %s%d ERROR%s' "$R$B" "$fail_n" "$N"
[ "$warn_n" -gt 0 ] && printf -- ' · %s%d WARN%s' "$Y$B" "$warn_n" "$N"
[ "$info_n" -gt 0 ] && printf -- ' · %s%d INFO%s' "$C$B" "$info_n" "$N"
[ "$blk_n" -gt 0 ] && printf -- ' · %s%d BLOCKER%s' "$M$B" "$blk_n" "$N"
printf '\n' printf '\n'
# overall verdict fi
# overall verdict — bold label, filled badge
if [ "$fail_n" -eq 0 ] && [ "$blk_n" -eq 0 ] && [ "$warn_n" -eq 0 ]; then if [ "$fail_n" -eq 0 ] && [ "$blk_n" -eq 0 ] && [ "$warn_n" -eq 0 ]; then
if [ "${BOX:-0}" = "1" ]; then if [ "${BOX:-0}" = "1" ]; then
printf -- ' %s┌ OK ┐%s %sall clear%s\n' "$BG_OK" "$N" "$G" "$N" printf -- ' %s┌ OK ┐%s %s%sall clear%s\n' "$BG_OK" "$N" "$G" "$B" "$N"
else else
printf -- ' %s[ OK ] all clear%s\n' "$G" "$N" printf -- ' %s[ OK ] %sall clear%s\n' "$G" "$B" "$N"
fi fi
elif [ "$fail_n" -eq 0 ] && [ "$blk_n" -eq 0 ]; then elif [ "$fail_n" -eq 0 ] && [ "$blk_n" -eq 0 ]; then
if [ "${BOX:-0}" = "1" ]; then if [ "${BOX:-0}" = "1" ]; then
printf -- ' %s┌ WARN ┐%s %swarnings only (no hard fail)%s\n' "$BG_WARN" "$N" "$Y" "$N" printf -- ' %s┌ WARN ┐%s %s%swarnings only (no hard fail)%s\n' "$BG_WARN" "$N" "$Y" "$B" "$N"
else else
printf -- ' %s[ WARN ] warnings only (no hard fail)%s\n' "$Y" "$N" printf -- ' %s[ WARN ] %swarnings only (no hard fail)%s\n' "$Y" "$B" "$N"
fi fi
else else
local _fail_msg local _fail_msg
_fail_msg=$(i18n_text 'failed — see list above') _fail_msg=$(i18n_text 'failed — see list above')
if [ "${BOX:-0}" = "1" ]; then if [ "${BOX:-0}" = "1" ]; then
printf -- ' %s┌ ERROR ┐%s %s%s%s\n' "$BG_ERR" "$N" "$R" "$_fail_msg" "$N" printf -- ' %s┌ ERROR ┐%s %s%s%s%s\n' "$BG_ERR" "$N" "$R" "$B" "$_fail_msg" "$N"
else else
printf -- ' %s[ ERROR ] %s%s\n' "$R" "$_fail_msg" "$N" printf -- ' %s[ ERROR ] %s%s%s\n' "$R" "$B" "$_fail_msg" "$N"
fi fi
fi fi
} }

View file

@ -278,13 +278,31 @@ def classify(line: str) -> str:
u = line.upper() u = line.upper()
low = line.lower() low = line.lower()
# section / summary headers before badge matching (they contain the word ERROR) # section / summary headers before badge matching (they contain the word ERROR)
if "SUMMARY" in u or "--- ERRORS" in u or "ERRORS ·" in u or "ERRORS (" in u: if re.search(r"[╔═║╚]|SUMMARY", u) and (
"SUMMARY" in u or "═══" in line or "" in line or "" in line
):
if "SUMMARY" in u or ("" in line and "" in line) or ("" in line and "" in line):
if "SUMMARY" in u or (not is_numbered_check_line(line) and ("" in line or "" in line or "" in line)):
if "SUMMARY" in u or re.match(r"^\s*[╔╚║]", line):
if "SUMMARY" in u:
return "sum-header"
if re.match(r"^\s*[╔╚]", line) and "" in line:
return "sum-header"
if re.match(r"^\s*║", line) and not is_numbered_check_line(line):
return "sum-header"
if "--- ERRORS" in u or "ERRORS ·" in u or "ERRORS (" in u:
return "meta" return "meta"
if "numbered checks" in low or "totals:" in low: if "numbered checks" in low:
return "meta"
# SUMMARY verdict chrome (not a check)
if "warnings only" in low or "all clear" in low or "failed — see" in low or "failed - see" in low:
return "meta" return "meta"
if "totals:" in low:
return "sum-totals"
# SUMMARY verdict chrome (not a check) — keep distinct for HTML boxes
if "warnings only" in low:
return "sum-verdict-warn"
if "all clear" in low:
return "sum-verdict-ok"
if "failed — see" in low or "failed - see" in low:
return "sum-verdict-err"
# host-agent tees converter "wrote … (errors=N …)" into the same log # host-agent tees converter "wrote … (errors=N …)" into the same log
if line.lstrip().lower().startswith("wrote ") and "errors=" in low: if line.lstrip().lower().startswith("wrote ") and "errors=" in low:
return "meta" return "meta"
@ -296,30 +314,32 @@ def classify(line: str) -> str:
if not is_numbered_check_line(line) and re.search( if not is_numbered_check_line(line) and re.search(
r"[\[┌]\s*BLOCK(?:ER)?\s*[\]┐]", u r"[\[┌]\s*BLOCK(?:ER)?\s*[\]┐]", u
): ):
return "meta" return "sum-block"
return "blocker" return "blocker"
# Only real check badges (not free-text "ERROR" inside commit messages, # Only real check badges (not free-text "ERROR" inside commit messages,
# and not SUMMARY count rows like " [ ERROR ] 3") # and not SUMMARY count rows like " [ ERROR ] 3")
if re.search(r"\s*ERROR\b|\[\s*ERROR\b", u): if re.search(r"\s*ERROR\b|\[\s*ERROR\b", u):
if not is_numbered_check_line(line): if not is_numbered_check_line(line):
return "meta" return "sum-err"
return "error" return "error"
if re.search(r"\bERROR monpages\b", line, re.I): if re.search(r"\bERROR monpages\b", line, re.I):
return "error" return "error"
if re.search(r"\s*WARN\b|\[\s*WARN\b", u): if re.search(r"\s*WARN\b|\[\s*WARN\b", u):
if not is_numbered_check_line(line): if not is_numbered_check_line(line):
return "meta" return "sum-warn"
return "warn" return "warn"
if re.search(r"\s*INFO\b|\[\s*INFO\b", u): if re.search(r"\s*INFO\b|\[\s*INFO\b", u):
if not is_numbered_check_line(line): if not is_numbered_check_line(line):
return "meta" return "sum-info"
return "info" return "info"
if re.search(r"\s*OK\b|\[\s*OK\b", u) or ( if re.search(r"\s*OK\b|\[\s*OK\b", u) or (
(" OK" in u or u.strip().startswith("OK")) and is_numbered_check_line(line) (" OK" in u or u.strip().startswith("OK")) and is_numbered_check_line(line)
): ):
if re.search(r"\s*OK\b|\[\s*OK\b", u) and not is_numbered_check_line(line): if re.search(r"\s*OK\b|\[\s*OK\b", u) and not is_numbered_check_line(line):
return "meta" return "sum-ok"
return "ok" return "ok"
if re.search(r"[\[┌]\s*BLOCK\b", u) and not is_numbered_check_line(line):
return "sum-block"
if u.strip().startswith("-- ") or u.strip().startswith("=="): if u.strip().startswith("-- ") or u.strip().startswith("=="):
return "section" return "section"
return "plain" return "plain"
@ -1866,6 +1886,57 @@ main {{ padding: 12px 14px 48px; max-width: 1200px; }}
.line.section {{ color: var(--sec); margin-top: 8px; font-weight: 600; }} .line.section {{ color: var(--sec); margin-top: 8px; font-weight: 600; }}
.line.meta {{ color: var(--dim); }} .line.meta {{ color: var(--dim); }}
.line.plain {{ color: var(--fg); }} .line.plain {{ color: var(--fg); }}
/* v1.14.0: SUMMARY box rows distinct bg + bold per severity */
.line.sum-header {{
color: #fff; font-weight: 800; letter-spacing: 0.04em;
background: linear-gradient(90deg, #1a3a1a 0%, #0a1a0a 100%);
border-left: 4px solid var(--ok); padding: 4px 8px; margin-top: 10px;
}}
.line.sum-ok {{
color: #b8f0b8; font-weight: 700;
background: #0d1f0d; border-left: 4px solid var(--ok);
padding: 3px 8px; margin: 2px 0;
}}
.line.sum-err {{
color: #ffc9c9; font-weight: 700;
background: #2a0a0a; border-left: 4px solid var(--err);
padding: 3px 8px; margin: 2px 0;
}}
.line.sum-warn {{
color: #1a1400; font-weight: 800;
background: #c9a227; border-left: 4px solid #8a7010;
padding: 3px 8px; margin: 2px 0;
}}
.line.sum-info {{
color: #c8e7ff; font-weight: 700;
background: #0a1520; border-left: 4px solid var(--info);
padding: 3px 8px; margin: 2px 0;
}}
.line.sum-block {{
color: #f5d0ff; font-weight: 700;
background: #1a0a1a; border-left: 4px solid var(--blocker);
padding: 3px 8px; margin: 2px 0;
}}
.line.sum-totals {{
color: #eee; font-weight: 700;
background: #1a1a1a; border-left: 4px solid #666;
padding: 4px 8px; margin: 4px 0 2px;
}}
.line.sum-verdict-ok {{
color: #b8f0b8; font-weight: 800;
background: #0d1f0d; border: 1px solid #2a5a2a;
padding: 4px 8px; margin: 4px 0 8px;
}}
.line.sum-verdict-warn {{
color: #1a1400; font-weight: 800;
background: #c9a227; border: 1px solid #8a7010;
padding: 4px 8px; margin: 4px 0 8px;
}}
.line.sum-verdict-err {{
color: #ffc9c9; font-weight: 800;
background: #2a0a0a; border: 1px solid #622;
padding: 4px 8px; margin: 4px 0 8px;
}}
a.jump {{ color: inherit; text-decoration: underline dotted; }} a.jump {{ color: inherit; text-decoration: underline dotted; }}
footer {{ footer {{
margin-top: 18px; color: var(--dim); font-size: 12px; margin-top: 18px; color: var(--dim); font-size: 12px;