From 28bd42ba7eb423dbaf281c5f2b50bdfe3b335ddd Mon Sep 17 00:00:00 2001 From: Vijay Yadav Date: Mon, 30 Mar 2026 18:27:49 -0400 Subject: [PATCH 1/2] fix: add pagination support for recap trace list (#418) - Add `listTracesPaginated()` method to Trace class returning page of traces with total count, offset, and limit metadata - Add `--offset` CLI option to `trace list` command for navigating past the first page of results - Wire CLI handler to use `listTracesPaginated()` instead of manual array slicing - Remove hardcoded `.slice(0, 50)` cap in TUI dialog so all traces are accessible via built-in scrolling - Show "Showing X-Y of N" pagination footer with next-page hint - Distinguish empty results (no traces exist) from offset-past-end (offset exceeds total count) with clear messaging Closes #418 Co-Authored-By: Vijay Yadav --- .../src/altimate/observability/tracing.ts | 24 ++++++++++++ packages/opencode/src/cli/cmd/trace.ts | 37 +++++++++++++++---- .../cmd/tui/component/dialog-trace-list.tsx | 2 +- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/altimate/observability/tracing.ts b/packages/opencode/src/altimate/observability/tracing.ts index c653826789..aa0b71d79d 100644 --- a/packages/opencode/src/altimate/observability/tracing.ts +++ b/packages/opencode/src/altimate/observability/tracing.ts @@ -982,6 +982,30 @@ export class Trace { } } + /** + * List traces with pagination support. + * Returns a page of traces plus total count for building pagination UI. + */ + static async listTracesPaginated( + dir?: string, + options?: { offset?: number; limit?: number }, + ): Promise<{ + traces: Array<{ sessionId: string; file: string; trace: TraceFile }> + total: number + offset: number + limit: number + }> { + const all = await Trace.listTraces(dir) + const offset = Math.max(0, options?.offset ?? 0) + const limit = Math.max(1, options?.limit ?? 20) + return { + traces: all.slice(offset, offset + limit), + total: all.length, + offset, + limit, + } + } + static async loadTrace(sessionId: string, dir?: string): Promise { const tracesDir = dir ?? DEFAULT_TRACES_DIR try { diff --git a/packages/opencode/src/cli/cmd/trace.ts b/packages/opencode/src/cli/cmd/trace.ts index 3c9a0b1b9b..cf35ee71c8 100644 --- a/packages/opencode/src/cli/cmd/trace.ts +++ b/packages/opencode/src/cli/cmd/trace.ts @@ -49,13 +49,23 @@ function truncate(str: string, len: number): string { } // altimate_change start — trace: list session traces (recordings/recaps of agent sessions) -function listTraces(traces: Array<{ sessionId: string; trace: TraceFile }>, tracesDir?: string) { - if (traces.length === 0) { +function listTraces( + traces: Array<{ sessionId: string; trace: TraceFile }>, + pagination: { total: number; offset: number; limit: number }, + tracesDir?: string, +) { + if (traces.length === 0 && pagination.total === 0) { UI.println("No traces found. Run a command with tracing enabled:") UI.println(" altimate-code run \"your prompt here\"") return } + if (traces.length === 0 && pagination.total > 0) { + UI.println(`No traces on this page (offset ${pagination.offset} past end of ${pagination.total} traces).`) + UI.println(UI.Style.TEXT_DIM + `Try: altimate-code trace list --offset 0 --limit ${pagination.limit}` + UI.Style.TEXT_NORMAL) + return + } + // Header const header = [ "DATE".padEnd(13), @@ -97,8 +107,13 @@ function listTraces(traces: Array<{ sessionId: string; trace: TraceFile }>, trac } UI.empty() - // altimate_change start — trace: session trace messages - UI.println(UI.Style.TEXT_DIM + `${traces.length} trace(s) in ${Trace.getTracesDir(tracesDir)}` + UI.Style.TEXT_NORMAL) + // altimate_change start — trace: session trace messages with pagination footer + const rangeStart = pagination.offset + 1 + const rangeEnd = pagination.offset + traces.length + UI.println(UI.Style.TEXT_DIM + `Showing ${rangeStart}-${rangeEnd} of ${pagination.total} trace(s) in ${Trace.getTracesDir(tracesDir)}` + UI.Style.TEXT_NORMAL) + if (rangeEnd < pagination.total) { + UI.println(UI.Style.TEXT_DIM + `Next page: altimate-code trace list --offset ${rangeEnd} --limit ${pagination.limit}` + UI.Style.TEXT_NORMAL) + } UI.println(UI.Style.TEXT_DIM + "View a trace: altimate-code trace view " + UI.Style.TEXT_NORMAL) // altimate_change end } @@ -134,6 +149,11 @@ export const TraceCommand = cmd({ describe: "number of traces to show", default: 20, }) + .option("offset", { + type: "number", + describe: "number of traces to skip (for pagination)", + default: 0, + }) .option("live", { type: "boolean", describe: "auto-refresh the viewer as the trace updates (for in-progress sessions)", @@ -148,8 +168,11 @@ export const TraceCommand = cmd({ const tracesDir = (cfg as any).tracing?.dir as string | undefined if (action === "list") { - const traces = await Trace.listTraces(tracesDir) - listTraces(traces.slice(0, args.limit || 20), tracesDir) + const page = await Trace.listTracesPaginated(tracesDir, { + offset: args.offset || 0, + limit: args.limit || 20, + }) + listTraces(page.traces, page, tracesDir) return } @@ -168,7 +191,7 @@ export const TraceCommand = cmd({ if (!match) { UI.error(`Trace not found: ${args.id}`) UI.println("Available traces:") - listTraces(traces.slice(0, 10), tracesDir) + listTraces(traces.slice(0, 10), { total: traces.length, offset: 0, limit: 10 }, tracesDir) process.exit(1) } diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-trace-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-trace-list.tsx index 0f6100a0d7..cfb07db9c5 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-trace-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-trace-list.tsx @@ -61,7 +61,7 @@ export function DialogTraceList(props: { }) } - result.push(...items.slice(0, 50).map((item) => { + result.push(...items.map((item) => { const rawStartedAt = item.trace.startedAt const parsedDate = typeof rawStartedAt === "string" || typeof rawStartedAt === "number" ? new Date(rawStartedAt) From a7968fc7f8ba0becaf2df162de0f360fec91cd19 Mon Sep 17 00:00:00 2001 From: Vijay Yadav Date: Mon, 30 Mar 2026 18:53:02 -0400 Subject: [PATCH 2/2] fix: normalize pagination inputs to finite integers Coerce offset/limit to finite integers via Number.isFinite and Math.trunc before using them for slice and returning in metadata. Prevents NaN, fractional, or infinite values from producing invalid display ranges. Co-Authored-By: Vijay Yadav --- packages/opencode/src/altimate/observability/tracing.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/observability/tracing.ts b/packages/opencode/src/altimate/observability/tracing.ts index aa0b71d79d..cc199f9d1c 100644 --- a/packages/opencode/src/altimate/observability/tracing.ts +++ b/packages/opencode/src/altimate/observability/tracing.ts @@ -996,8 +996,10 @@ export class Trace { limit: number }> { const all = await Trace.listTraces(dir) - const offset = Math.max(0, options?.offset ?? 0) - const limit = Math.max(1, options?.limit ?? 20) + const rawOffset = options?.offset ?? 0 + const rawLimit = options?.limit ?? 20 + const offset = Number.isFinite(rawOffset) ? Math.max(0, Math.trunc(rawOffset)) : 0 + const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.trunc(rawLimit)) : 20 return { traces: all.slice(offset, offset + limit), total: all.length,