Skip to content

Commit 660b47a

Browse files
committed
fix(gui): surface Logs failures when refresh is off and resync section tabs
Announce stale-log errors immediately when auto-refresh is disabled, and pick the heading nearest the reading line when a scroll-lock timeout fires.
1 parent c0974f3 commit 660b47a

2 files changed

Lines changed: 15 additions & 10 deletions

File tree

gui/src/components/section-tabs.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,19 @@ export function SectionTabs({
4343
/** Timeout path: drop the click lock and re-read the visible section. */
4444
const expireScrollLock = useCallback(() => {
4545
clearScrollLock();
46-
// If the user interrupted smooth scroll, the target may never intersect — without a
47-
// resync `active` would stay on the clicked tab while another section is on screen.
46+
// If the user interrupted smooth scroll, the target may never cross the observer threshold —
47+
// without a resync `active` would stay on the clicked tab while another section is on screen.
48+
// Prefer the heading nearest the sticky-strip reading line (not only those with top <= 120),
49+
// so a destination that stopped mid-viewport still wins over an off-screen prior heading.
4850
let bestId: string | null = null;
49-
let bestTop = Number.NEGATIVE_INFINITY;
51+
let bestDistance = Number.POSITIVE_INFINITY;
52+
const readingLine = 72;
5053
for (const item of items) {
5154
const node = document.getElementById(sectionAnchorId(scope, item.id));
5255
if (!node) continue;
53-
const top = node.getBoundingClientRect().top;
54-
// Match the observer bias: prefer a heading near the top of the viewport.
55-
if (top <= 120 && top > bestTop) {
56-
bestTop = top;
56+
const distance = Math.abs(node.getBoundingClientRect().top - readingLine);
57+
if (distance < bestDistance) {
58+
bestDistance = distance;
5759
bestId = item.id;
5860
}
5961
}

gui/src/pages/Logs.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,10 @@ export default function Logs({ apiBase }: { apiBase: string }) {
468468
} else if (settledFailure && failureStreak.error !== logsState.error) {
469469
setFailureStreak(previous => ({ error: logsState.error, count: previous.count + 1 }));
470470
}
471-
const pollFailing = failureStreak.count >= STALE_POLL_FAILURE_LIMIT;
471+
// Auto-refresh off: one settled failure is enough — there is no next poll to recover quietly.
472+
const pollFailing =
473+
failureStreak.count >= STALE_POLL_FAILURE_LIMIT
474+
|| (!autoRefresh && settledFailure);
472475

473476
const detailInfo = detail ? statusCodeInfo(detail.status, locale) : null;
474477
const conversationQuery = conversationFilter.trim();
@@ -635,8 +638,8 @@ export default function Logs({ apiBase }: { apiBase: string }) {
635638
</button>
636639
</Notice>
637640
)}
638-
{/* A run of failed polls is no longer transient: say the rows below are stale rather than
639-
letting them read as current. Cleared by the first successful poll. */}
641+
{/* Stale rows after a sustained poll outage, or any settled failure while auto-refresh is
642+
off (no next tick will recover). Cleared by the first successful fetch. */}
640643
{pollFailing && logs.length > 0 && (
641644
<Notice tone="err">
642645
{t("logs.loadError")}{" "}

0 commit comments

Comments
 (0)