Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

### Fixes
- Session log viewer: stop rendering log entries at the wrong y-offset (which exposed the page background and looked like the page "going blue" while scrolling). `app/components/raw-log-viewer.tsx` was capturing the virtualizer's `scrollMargin` once via a callback ref on the list wrapper's `offsetTop`. That ref fires only on mount, so any layout shift above the list — most commonly the async `searchHookActivityAction` resolving and mounting the `<SessionHooksPanel>` panel above the Logs section, but also Subagents-section / per-subagent collapse-expand, and window resizes that re-flow the StatsBar / ToolStatsGrid — left `scrollMargin` stale. The `useWindowVirtualizer` then computed the wrong visible window in list-local coordinates and positioned each item at `transform: translateY(virtualRow.start - staleScrollMargin)`, so items appeared shifted by the layout-delta (typically tens to hundreds of pixels). Replace the callback ref with a stable `useRef<HTMLDivElement>` and a `useLayoutEffect` that reads `getBoundingClientRect().top + window.scrollY` (more robust than `offsetTop` against future positioned ancestors) and re-reads it from a `ResizeObserver` watching `document.body` plus a `window` resize listener. Functional `setScrollMargin(prev => prev === top ? prev : top)` short-circuits same-value updates so the body-resize that the state-update itself causes can't loop. (#292)
- Detect when `failproofai` on the user's PATH is shadowed by a different, older install (classic cause: a leftover `bun link` from a prior dev session, or a previously-installed `bun install -g failproofai` whose `~/.bun/bin` prefix sorts ahead of npm's). New `scripts/install-diagnosis.mjs` helper resolves the PATH-first install via `command -v` (POSIX) / `where` (Win32), compares its package root + version against the running install, and surfaces a copy-pasteable cleanup command (`rm -f ~/.bun/bin/failproofai && rm -rf ~/.bun/install/global/node_modules/failproofai` for bun-side shadows, `npm rm -g failproofai` for npm-side ones). Wired into two places: (1) `scripts/postinstall.mjs` warns at install time when the just-installed copy is being shadowed, before the customer ever sees the runtime error, (2) `scripts/launch.ts` rewrites the existing "Cannot find server.js at" error to point at the actual stale install (with both versions and the cleanup command) when the missing build output is caused by a PATH shadow rather than a genuinely broken build. Replaces the previous misleading recommendation (`npm install -g failproofai@latest`) which doesn't help when the new install is itself being shadowed (#286).

## 0.0.10-beta.0 — 2026-05-04
Expand Down
30 changes: 25 additions & 5 deletions app/components/raw-log-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
"use client";

import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { useWindowVirtualizer } from "@tanstack/react-virtual";
import { ChevronDown, Wrench } from "lucide-react";
import type { LogEntry, ToolUseBlock } from "@/lib/log-entries";
Expand Down Expand Up @@ -209,9 +209,29 @@ function VirtualizedEntryList({ entries, entriesBySource, projectName, sessionId
const [highlightedUuid, setHighlightedUuid] = useState<string | null>(() => parseHashUuid());
const highlightTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
const hasScrolledRef = useRef(false);

const listCallbackRef = useCallback((node: HTMLDivElement | null) => {
if (node) setScrollMargin(node.offsetTop);
const listRef = useRef<HTMLDivElement>(null);

// Keep `scrollMargin` in sync with the list's actual document offset.
// A one-shot callback ref would go stale whenever something above the list
// shifts (async hook-activity panel mounting, subagent collapse/expand,
// window resize) — leaving the virtualizer to render items at the wrong
// y-offset and exposing the page background under the (otherwise empty)
// virtualized container.
useLayoutEffect(() => {
const el = listRef.current;
if (!el) return;
const update = () => {
const top = el.getBoundingClientRect().top + window.scrollY;
setScrollMargin((prev) => (prev === top ? prev : top));
};
update();
const ro = new ResizeObserver(update);
ro.observe(document.body);
window.addEventListener("resize", update);
return () => {
ro.disconnect();
window.removeEventListener("resize", update);
};
}, []);

const segments = useMemo(() => computeSegments(entries), [entries]);
Expand Down Expand Up @@ -337,7 +357,7 @@ function VirtualizedEntryList({ entries, entriesBySource, projectName, sessionId
}, [highlightedUuid, parentUuidForSubagent, uuidToIndex, virtualizer]);

return (
<div ref={listCallbackRef}>
<div ref={listRef}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
Expand Down
Loading