feat(perf): live runtime logs tab in Performance Probe (#946)

* feat(perf): live runtime logs tab in Performance Probe

Add a Logs tab that streams the backend runtime log ring buffer in-app, so the
stdout/stderr console (unreachable on Windows without exporting) can be read
live. The buffer now tracks a monotonic seq; a new tail_runtime_logs command
returns lines incrementally and get_logging_mode reports the current depth.

The tab has a depth switch (off/normal/debug) mirroring app settings, a line cap
(500-5000), pause/clear, auto-follow, and an ordered comma-separated word filter
where a plain word includes and a -word excludes, applied left to right as
layers (sequence matters).

* docs(changelog): note Performance Probe logs tab (PR #946)

Add CHANGELOG entry and credits line for the live runtime logs tab.

* fix(perf): pin log view position when scrolled up

Auto-scroll keeps the logs tab at the tail, but once the user scrolls up the
view now stays put — the previously-topmost line is re-pinned each tick while
the log keeps appending below for further scrolling. History under the viewport
is no longer trimmed while scrolled up (kept up to the ring-buffer ceiling); the
cap is re-applied when following resumes. Buffer overflow is shown in the status
line instead of an injected marker row.

* fix(perf): scope logs tab to its own internal scroll

The whole probe body scrolled (controls + filter + log) because the log
container sized via height:100%, which WebKitGTK does not resolve against the
flex body. Make the body a flex column with hidden overflow on the Logs tab and
let the log view flex-fill, so depth/keep/pause/clear and the filter stay fixed
while only the log lines scroll.
This commit is contained in:
cucadmuh
2026-06-02 11:40:36 +03:00
committed by GitHub
parent c6df05e576
commit 975bb6d9af
12 changed files with 666 additions and 17 deletions
@@ -1,14 +1,15 @@
import { useState } from 'react';
import { Activity, SlidersHorizontal, X } from 'lucide-react';
import { Activity, ScrollText, SlidersHorizontal, X } from 'lucide-react';
import { createPortal } from 'react-dom';
import SidebarPerfProbeMonitorTab from './perfProbe/SidebarPerfProbeMonitorTab';
import SidebarPerfProbeTogglesTab from './perfProbe/SidebarPerfProbeTogglesTab';
import SidebarPerfProbeLogsTab from './perfProbe/SidebarPerfProbeLogsTab';
import { resetPerfProbeFlags, type PerfProbeFlags } from '../../utils/perf/perfFlags';
import { clearPerfLiveOverlayPins } from '../../utils/perf/perfOverlayPins';
import { resetPerfOverlayAppearance } from '../../utils/perf/perfOverlayAppearance';
import { resetPerfOverlayMode } from '../../utils/perf/perfOverlayMode';
type TabId = 'monitor' | 'toggles';
type TabId = 'monitor' | 'toggles' | 'logs';
interface Props {
open: boolean;
@@ -88,12 +89,21 @@ export default function SidebarPerfProbeModal({
<SlidersHorizontal size={15} />
Toggles
</button>
<button
type="button"
role="tab"
aria-selected={tab === 'logs'}
className={`sidebar-perf-modal__tab${tab === 'logs' ? ' sidebar-perf-modal__tab--active' : ''}`}
onClick={() => setTab('logs')}
>
<ScrollText size={15} />
Logs
</button>
</div>
<div className="sidebar-perf-modal__body">
{tab === 'monitor' ? (
<SidebarPerfProbeMonitorTab />
) : (
<div className={`sidebar-perf-modal__body${tab === 'logs' ? ' sidebar-perf-modal__body--logs' : ''}`}>
{tab === 'monitor' && <SidebarPerfProbeMonitorTab />}
{tab === 'toggles' && (
<SidebarPerfProbeTogglesTab
perfFlags={perfFlags}
hotCacheEnabled={hotCacheEnabled}
@@ -104,6 +114,7 @@ export default function SidebarPerfProbeModal({
setLoggingMode={setLoggingMode}
/>
)}
{tab === 'logs' && <SidebarPerfProbeLogsTab />}
</div>
<div className="sidebar-perf-modal__actions">