Skip to content

Commit 257eb95

Browse files
committed
Paint the full retained transcript instead of a smaller window
The stream log is already capped at a fixed number of retained rows with an absolute base index threaded through every touch point, so a second, smaller paint window on top of that cap was redundant: painting every retained row is what makes all of it reachable by scrolling, and it composes with the retention cap instead of duplicating it. The paint tree now rebuilds directly from the retained log (no separate windowing pass or collapse marker). Appending a row adds one node and removes only what the retention trim actually evicted. Replacing a row in place — the streaming-token path — still resolves to a single-node retext once eviction has started; the row-children helper now excludes the eviction notice by node identity rather than by array length, so the notice's presence can never make that check look like a broken mapping and force a full rebuild. Evicted rows get a notice above the oldest retained one, updated in place rather than rebuilt, so the boundary reads as dropped rather than as the true start of history. Deleted the old windowing helpers and their dedicated test file — once the log itself is capped, nothing calls them.
1 parent b2b6848 commit 257eb95

5 files changed

Lines changed: 277 additions & 277 deletions

File tree

src/tui-opentui/long-log.test.ts

Lines changed: 0 additions & 111 deletions
This file was deleted.

src/tui-opentui/long-log.ts

Lines changed: 5 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,18 @@
11
/**
2-
* Long-log window strategy — keep multi-thousand-line sessions interactive.
3-
* Pure slice math; shell paints only the window, not the full history.
4-
*
5-
* Budget (Wave 6 defaults; CL-5399 may refine):
6-
* - Painted window: last N rows (or pin around offset)
7-
* - Collapse threshold: when history exceeds this, older rows stay in the
8-
* model but drop from the render tree until scrolled into the window
2+
* Retention budget for a long-running transcript. The paint tree tracks
3+
* `streamLog` 1:1 (see shell.ts's repaintTranscriptWindow/paintAppendStreamRow)
4+
* so every retained row stays reachable by scrolling; this cap is what keeps
5+
* that array — and so the paint tree — bounded over a long session.
96
*/
107

11-
import type { StreamRow } from "./stream.js"
12-
13-
/** Rows kept in the paint tree under normal follow-tail. */
14-
export const LONG_LOG_WINDOW = 200
15-
16-
/**
17-
* When total rows exceed this, append/scroll paths must use windowSlice
18-
* (never re-paint the full history).
19-
*/
20-
export const LONG_LOG_COLLAPSE_THRESHOLD = 500
21-
228
/**
239
* Retained tail of a stream log. Display-only state — the agent's own context
2410
* is kept separately — but an unbounded array still costs memory and O(n)
25-
* snapshot/diff work on every append over a long, tool-heavy session. Set
26-
* above the collapse threshold so eviction never fights the paint window.
11+
* snapshot/diff work on every append over a long, tool-heavy session.
2712
*/
2813
export const MAX_RETAINED_STREAM_ROWS = 600
2914

3015
/** Rows to drop from the front of a log of this length to fit the cap. */
3116
export function retentionOverflow(length: number): number {
3217
return Math.max(0, length - MAX_RETAINED_STREAM_ROWS)
3318
}
34-
35-
export type LongLogWindow = {
36-
/** Inclusive start index into the full row log. */
37-
readonly start: number
38-
/** Exclusive end index. */
39-
readonly end: number
40-
/** Slice of rows to paint. */
41-
readonly rows: readonly StreamRow[]
42-
/** True when older rows exist above the window. */
43-
readonly truncatedAbove: boolean
44-
/** True when newer rows exist below the window (pinned). */
45-
readonly truncatedBelow: boolean
46-
/** Full log length. */
47-
readonly total: number
48-
}
49-
50-
export type WindowSliceOpts = {
51-
/** Max rows to include (default LONG_LOG_WINDOW). */
52-
readonly windowSize?: number
53-
/**
54-
* Pin the window so this index is visible (keep-active-visible style).
55-
* When omitted, follow the tail (last windowSize rows).
56-
*/
57-
readonly pinIndex?: number
58-
}
59-
60-
/**
61-
* Compute which rows to paint for a long log.
62-
* Follow-tail by default; pinIndex keeps a historical row in view.
63-
*/
64-
export function windowSlice(
65-
log: readonly StreamRow[],
66-
opts?: WindowSliceOpts,
67-
): LongLogWindow {
68-
const total = log.length
69-
const windowSize = Math.max(1, Math.floor(opts?.windowSize ?? LONG_LOG_WINDOW))
70-
71-
if (total === 0) {
72-
return {
73-
start: 0,
74-
end: 0,
75-
rows: [],
76-
truncatedAbove: false,
77-
truncatedBelow: false,
78-
total: 0,
79-
}
80-
}
81-
82-
let end: number
83-
let start: number
84-
85-
if (opts?.pinIndex !== undefined) {
86-
const pin = Math.max(0, Math.min(total - 1, Math.floor(opts.pinIndex)))
87-
// Center-ish: keep pin in window; prefer showing context after pin when possible.
88-
start = Math.max(0, pin - Math.floor(windowSize / 2))
89-
end = Math.min(total, start + windowSize)
90-
start = Math.max(0, end - windowSize)
91-
} else {
92-
// Follow tail
93-
end = total
94-
start = Math.max(0, total - windowSize)
95-
}
96-
97-
return {
98-
start,
99-
end,
100-
rows: log.slice(start, end),
101-
truncatedAbove: start > 0,
102-
truncatedBelow: end < total,
103-
total,
104-
}
105-
}
106-
107-
/** Whether the log is large enough that windowing is mandatory. */
108-
export function mustWindow(totalRows: number): boolean {
109-
return totalRows > LONG_LOG_COLLAPSE_THRESHOLD
110-
}
111-
112-
/**
113-
* Collapse marker line for the paint tree when truncatedAbove.
114-
* Pure string — shell styles it as system chrome.
115-
*/
116-
export function collapseMarker(above: number): string {
117-
const n = Math.max(0, Math.floor(above))
118-
if (n <= 0) return ""
119-
return `… ${n} earlier line${n === 1 ? "" : "s"} collapsed`
120-
}

0 commit comments

Comments
 (0)