Skip to content

Commit c71dc4a

Browse files
fix: show actual turn count and add load-more pagination in context debugger (#8)
The header displayed the paginated page size (e.g. "100 turns") instead of the real total. Now uses head_depth from the API response metadata to show the true count, with "X of Y turns" when truncated by the limit. Adds a "Load older turns" button at the top of the turn list when a context has more turns than the fetch limit. Uses the existing before_turn_id API cursor to fetch and prepend older pages. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8216ca3 commit c71dc4a

1 file changed

Lines changed: 47 additions & 6 deletions

File tree

frontend/components/ContextDebugger.tsx

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ interface ContextDebuggerProps {
437437
onNavigateToContext?: (contextId: string) => void;
438438
}
439439

440+
const TURNS_PAGE_SIZE = 100;
441+
440442
export function ContextDebugger({ contextId, isOpen, onClose, lastEvent, initialTurnId, onTurnChange, onNavigateToContext }: ContextDebuggerProps) {
441443
const containerRef = useRef<HTMLDivElement | null>(null);
442444
const turnListRef = useRef<HTMLDivElement | null>(null);
@@ -447,6 +449,7 @@ export function ContextDebugger({ contextId, isOpen, onClose, lastEvent, initial
447449

448450
// Data fetching state
449451
const [loading, setLoading] = useState(false);
452+
const [loadingMore, setLoadingMore] = useState(false);
450453
const [error, setError] = useState<string | null>(null);
451454
const [data, setData] = useState<TurnResponse | null>(null);
452455

@@ -474,7 +477,7 @@ export function ContextDebugger({ contextId, isOpen, onClose, lastEvent, initial
474477

475478
try {
476479
const response = await fetchTurns(contextId, {
477-
limit: 100,
480+
limit: TURNS_PAGE_SIZE,
478481
view: 'typed',
479482
include_unknown: true,
480483
});
@@ -491,6 +494,32 @@ export function ContextDebugger({ contextId, isOpen, onClose, lastEvent, initial
491494
}
492495
}, [contextId]);
493496

497+
// Load older turns using pagination cursor
498+
const loadMore = useCallback(async () => {
499+
if (!contextId || !data?.next_before_turn_id) return;
500+
501+
setLoadingMore(true);
502+
try {
503+
const response = await fetchTurns(contextId, {
504+
limit: TURNS_PAGE_SIZE,
505+
before_turn_id: data.next_before_turn_id,
506+
view: 'typed',
507+
include_unknown: true,
508+
});
509+
const prepended = response.turns.length;
510+
setData(prev => prev ? {
511+
...prev,
512+
turns: [...response.turns, ...prev.turns],
513+
next_before_turn_id: response.next_before_turn_id,
514+
} : response);
515+
setSelectedIdx(prev => prev + prepended);
516+
} catch {
517+
// Keep existing data on failure
518+
} finally {
519+
setLoadingMore(false);
520+
}
521+
}, [contextId, data?.next_before_turn_id]);
522+
494523
useEffect(() => {
495524
if (isOpen && contextId) {
496525
loadTurns();
@@ -617,7 +646,7 @@ export function ContextDebugger({ contextId, isOpen, onClose, lastEvent, initial
617646

618647
// Count stats - count both tool_call turns AND tool_calls embedded in assistant turns
619648
const stats = useMemo(() => {
620-
if (!data?.turns) return { total: 0, toolCalls: 0, errors: 0 };
649+
if (!data?.turns) return { total: 0, loaded: 0, toolCalls: 0, errors: 0 };
621650
let toolCalls = 0;
622651
let errors = 0;
623652
for (const turn of data.turns) {
@@ -632,7 +661,8 @@ export function ContextDebugger({ contextId, isOpen, onClose, lastEvent, initial
632661
const result = extractToolResult(turn);
633662
if (result?.isError) errors++;
634663
}
635-
return { total: data.turns.length, toolCalls, errors };
664+
const totalDepth = (data.meta?.head_depth ?? -1) + 1;
665+
return { total: totalDepth, loaded: data.turns.length, toolCalls, errors };
636666
}, [data]);
637667

638668
// Auto-select last turn when following and new turns arrive
@@ -759,7 +789,7 @@ export function ContextDebugger({ contextId, isOpen, onClose, lastEvent, initial
759789
</div>
760790
{data && (
761791
<div className="flex items-center gap-3 text-xs text-theme-text-dim">
762-
<span>{stats.total} turns</span>
792+
<span>{stats.loaded < stats.total ? `${stats.loaded} of ${stats.total} turns` : `${stats.total} turns`}</span>
763793
<span>{stats.toolCalls} tool calls</span>
764794
{stats.errors > 0 && (
765795
<span className="text-red-400">{stats.errors} errors</span>
@@ -836,7 +866,17 @@ export function ContextDebugger({ contextId, isOpen, onClose, lastEvent, initial
836866
{data?.turns.length === 0 ? 'No turns.' : 'No matches.'}
837867
</div>
838868
) : (
839-
filteredTurns.map((turn, idx) => {
869+
<>
870+
{data && data.turns.length > 0 && data.turns[0].depth > 0 && (
871+
<button
872+
onClick={loadMore}
873+
disabled={loadingMore}
874+
className="w-full px-3 py-2 text-xs text-theme-accent hover:bg-theme-bg-tertiary/40 border-b border-theme-border-dim/60 transition-colors disabled:opacity-50"
875+
>
876+
{loadingMore ? 'Loading...' : `Load older turns (${data.turns[0].depth} remaining)`}
877+
</button>
878+
)}
879+
{filteredTurns.map((turn, idx) => {
840880
const kind = detectTurnKind(turn);
841881
const colors = getKindColors(kind);
842882
const isSelected = idx === selectedIdx;
@@ -880,7 +920,8 @@ export function ContextDebugger({ contextId, isOpen, onClose, lastEvent, initial
880920
</div>
881921
</button>
882922
);
883-
})
923+
})}
924+
</>
884925
)}
885926

886927
{/* Resume following indicator */}

0 commit comments

Comments
 (0)