diff --git a/web/public/css/iblogs.css b/web/public/css/iblogs.css index 62f2204..fa34159 100644 --- a/web/public/css/iblogs.css +++ b/web/public/css/iblogs.css @@ -1153,6 +1153,17 @@ main { 0 6px 16px color-mix(in srgb, var(--accent) 25%, transparent); } +/* + * The header error-count chip is informational only — the smart fold is + * applied automatically on page load and folds can only be expanded + * individually. Strip the .btn cursor / hover affordance so the chip + * doesn't read as interactive. + */ +#error-toggle { + cursor: default; + pointer-events: none; +} + .log-inner .level { display: block; white-space: pre-wrap; diff --git a/web/public/js/log.js b/web/public/js/log.js index a8c9019..b2dda75 100644 --- a/web/public/js/log.js +++ b/web/public/js/log.js @@ -48,48 +48,44 @@ function scrollToHeight(top, smoothScrollLimit = 10000) { } /* - * Error collapse toggle. + * Smart fold around errors. * - * Smart fold: every entry within ±ERROR_WINDOW_SIZE of an error stays - * visible; runs of non-error entries with no nearby errors collapse into - * a draggable fold bar. Vertical drag on the bar progressively reveals or - * re-hides lines; a click without drag reveals the next DEFAULT_CLICK_REVEAL - * lines. + * Every entry within ±ERROR_WINDOW_SIZE of an error stays visible; runs of + * non-error entries with no nearby errors collapse into a draggable fold + * bar. Vertical drag on the bar progressively reveals or re-hides lines; + * a click without drag reveals the next DEFAULT_CLICK_REVEAL lines. + * + * The fold is applied automatically on page load when the log contains + * errors. Folds can only be expanded individually — there is no + * "unfold everything" path. The error-count chip in the header is purely + * informational; it is not interactive. */ const ERROR_WINDOW_SIZE = 25; const PIXELS_PER_LINE_DRAG = 6; const DEFAULT_CLICK_REVEAL = 25; const DRAG_PIXEL_THRESHOLD = 3; -const toggleErrorsButton = document.getElementById("error-toggle"); -if (toggleErrorsButton) { - toggleErrorsButton.addEventListener("click", toggleErrors); -} +applySmartFold(); -function toggleErrors() { - if (toggleErrorsButton.classList.contains("toggled")) { - toggleErrorsButton.classList.remove("toggled"); - uncollapseAllErrors(); - } else { - toggleErrorsButton.classList.add("toggled"); - collapseAllErrors(); - } -} - -function collapseAllErrors() { +function applySmartFold() { const lines = Array.from(document.querySelectorAll('.log-inner > .entry')); const total = lines.length; if (!total) return; - // Pass 1: mark entries within ±ERROR_WINDOW_SIZE of any error. + // Pass 1: mark entries within ±ERROR_WINDOW_SIZE of any error. If the + // log has no errors, every entry stays visible (we never produce one + // giant fold spanning the whole log). const mustShow = new Array(total).fill(false); + let sawError = false; for (let i = 0; i < total; i++) { if (lines[i].classList.contains("entry-error")) { + sawError = true; const lo = Math.max(0, i - ERROR_WINDOW_SIZE); const hi = Math.min(total - 1, i + ERROR_WINDOW_SIZE); for (let j = lo; j <= hi; j++) mustShow[j] = true; } } + if (!sawError) return; // Pass 2: hide unmarked entries; emit a fold bar at the START of each run. let runEntries = []; @@ -110,11 +106,6 @@ function collapseAllErrors() { flushRun(); } -function uncollapseAllErrors() { - document.querySelectorAll(".log-inner > .entry").forEach(line => line.style.removeProperty("display")); - document.querySelectorAll(".collapsed-lines").forEach(bar => bar.remove()); -} - function createFoldBar(entries) { const bar = document.createElement("div"); bar.classList.add("collapsed-lines", "collapsed-lines-foldable");