feat(web): display logs newest-first in web gateway UI (#369)

Reverse log display order so the most recent entries appear at the top,
removing the need to scroll to see latest activity.

Frontend: rename appendLogEntry to prependLogEntry, use prepend() for
DOM insertion, cap oldest entries from the bottom, and auto-scroll to
top. Backend: update recent_entries() doc comment to clarify the
oldest-first return order works correctly with the frontend's prepend.

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
Henry Park
2026-02-25 16:00:09 -08:00
committed by GitHub
co-authored by Claude Opus 4.6
parent db2ba424ce
commit 0c5f082d16
3 changed files with 19 additions and 9 deletions
Generated
+7
View File
@@ -5032,6 +5032,12 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sha1_smol"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.9" version = "0.10.9"
@@ -6207,6 +6213,7 @@ dependencies = [
"getrandom 0.4.1", "getrandom 0.4.1",
"js-sys", "js-sys",
"serde_core", "serde_core",
"sha1_smol",
"wasm-bindgen", "wasm-bindgen",
] ]
+3
View File
@@ -90,6 +90,9 @@ impl LogBroadcaster {
} }
/// Snapshot of recent entries for replaying to a new subscriber. /// Snapshot of recent entries for replaying to a new subscriber.
///
/// Returns entries oldest-first so that the frontend's `prepend()`
/// naturally places the newest entry at the top of the DOM.
pub fn recent_entries(&self) -> Vec<LogEntry> { pub fn recent_entries(&self) -> Vec<LogEntry> {
self.recent self.recent
.lock() .lock()
+9 -9
View File
@@ -1105,7 +1105,7 @@ function connectLogSSE() {
logBuffer.push(entry); logBuffer.push(entry);
return; return;
} }
appendLogEntry(entry); prependLogEntry(entry);
}); });
logEventSource.onerror = () => { logEventSource.onerror = () => {
@@ -1113,7 +1113,7 @@ function connectLogSSE() {
}; };
} }
function appendLogEntry(entry) { function prependLogEntry(entry) {
const output = document.getElementById('logs-output'); const output = document.getElementById('logs-output');
// Level filter // Level filter
@@ -1154,16 +1154,16 @@ function appendLogEntry(entry) {
div.style.display = 'none'; div.style.display = 'none';
} }
output.appendChild(div); output.prepend(div);
// Cap entries // Cap entries (remove oldest at the bottom)
while (output.children.length > LOG_MAX_ENTRIES) { while (output.children.length > LOG_MAX_ENTRIES) {
output.removeChild(output.firstChild); output.removeChild(output.lastChild);
} }
// Auto-scroll // Auto-scroll to top (newest entries are at the top)
if (document.getElementById('logs-autoscroll').checked) { if (document.getElementById('logs-autoscroll').checked) {
output.scrollTop = output.scrollHeight; output.scrollTop = 0;
} }
} }
@@ -1173,9 +1173,9 @@ function toggleLogsPause() {
btn.textContent = logsPaused ? 'Resume' : 'Pause'; btn.textContent = logsPaused ? 'Resume' : 'Pause';
if (!logsPaused) { if (!logsPaused) {
// Flush buffer // Flush buffer: oldest-first + prepend naturally puts newest at top
for (const entry of logBuffer) { for (const entry of logBuffer) {
appendLogEntry(entry); prependLogEntry(entry);
} }
logBuffer = []; logBuffer = [];
} }