Compare commits

...

4 Commits

Author SHA1 Message Date
240635d30c Merge branch 'entry-content-visibility'
Some checks failed
Publish Docker Image / build-and-push (push) Failing after 3s
2026-05-06 19:39:01 +00:00
47303d6812 perf: skip render of off-screen log cells via content-visibility
With logs up to ~25 000 entries, eagerly painting every grid cell
caused multi-second freezes on page load. Add content-visibility:
auto to the line-number and content cells so the browser defers
their layout / paint until they scroll into view.

The rule lives on the cells (not on .entry itself) because .entry
uses display: contents and produces no box of its own.
contain-intrinsic-size: auto 1.5em lets the browser remember
measured heights after first paint and uses ~one line tall as the
initial placeholder for never-seen cells.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:39:01 +00:00
aeef6bc0cd Merge branch 'auto-fold-on-load'
Some checks failed
Publish Docker Image / build-and-push (push) Failing after 4s
2026-05-06 19:31:31 +00:00
415324f340 feat: apply smart fold automatically on page load
Logs with errors now open already smart-folded — every entry within
±25 of an error stays visible, gaps collapse into draggable bars.
Folds can only be expanded individually via per-bar click or
drag; the previous "toggle to unfold everything" path is gone. The
header error-count chip becomes informational only (cursor and
pointer-events stripped so it no longer reads as interactive).

Removed the dead toggleErrors / uncollapseAllErrors / "toggled"
state plumbing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:31:31 +00:00
2 changed files with 47 additions and 27 deletions

View File

@@ -950,6 +950,24 @@ main {
width: 100%;
}
/*
* Skip layout / paint of off-screen log cells until they scroll into
* view. With logs running up to ~25 000 entries the eager render of
* every cell is what causes the multi-second freeze on initial load.
*
* `.entry` itself is `display: contents` (no box), so the rule has to
* land on the actual grid items it produces. `contain-intrinsic-size:
* auto 1.5em` lets the browser remember measured heights after first
* paint and falls back to ~one line tall as the placeholder for
* never-seen cells. Multi-line stack traces correct on first pass; the
* scroll position adjusts once.
*/
.log-inner .line-number-container,
.log-inner .line-content {
content-visibility: auto;
contain-intrinsic-size: auto 1.5em;
}
.log-inner .entry.entry-error .line-content,
.log-inner .entry.entry-error .line-number-container{
background-color: var(--error-bg);
@@ -1153,6 +1171,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;

View File

@@ -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");