Skip to content

Commit 61ac3f9

Browse files
Merge full retained transcript painting
2 parents ef7d22a + 257eb95 commit 61ac3f9

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)