Skip to content

Commit 04d82ec

Browse files
var-ggclaude
andcommitted
fix(timeline): bound the loaded window with far-edge eviction
The all-repos windowed hook documents itself as keeping "ONE window", but extendOlder / extendNewer appended / prepended without ever evicting — a long uninterrupted scroll accumulated the whole walked history in React state, against the unbounded-scale mandate. The window is now capped at MAX_WINDOW rows. Extending past the cap drops the rows now far from the viewport — extendOlder evicts the top and bumps baseIndex, extendNewer evicts the bottom — recomputing the edge cursor from the new edge row (a cursor is reconstructable straight from a CommitSummary now that it is keyed on repo_path). The count-tall scroll track keeps evicted rows at their true global positions, so scrolling back simply re-loads them. tsc + vite build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 618d895 commit 04d82ec

1 file changed

Lines changed: 39 additions & 9 deletions

File tree

src/lib/useTimelineWindow.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ const ANCHOR_AFTER = 150;
3636
const EXTEND_GAP = 120;
3737
/** Collapse a flurry of scroll events (a scrollbar drag) into one jump. */
3838
const JUMP_DEBOUNCE_MS = 100;
39+
/** Hard cap on the loaded window. Extending past it evicts the far edge,
40+
* so an uninterrupted scroll can't accumulate the whole history in
41+
* memory — the window stays bounded regardless of scroll distance. */
42+
const MAX_WINDOW = 600;
3943

4044
/** Stable identity for a commit across reloads. */
4145
function commitKey(c: { repoPath: string; hash: string }): string {
@@ -45,6 +49,13 @@ function nowSec(): number {
4549
return Math.floor(Date.now() / 1000);
4650
}
4751

52+
/** A commit's keyset cursor, reconstructed from the row itself — the cursor
53+
* is (sort_ts, repo_path, hash) with sort_ts = -timestamp, all carried by
54+
* the CommitSummary. Used to recompute an edge cursor after a window trim. */
55+
function cursorOf(c: CommitSummary): Cursor {
56+
return { sortTs: -c.timestamp, repoPath: c.repoPath, hash: c.hash };
57+
}
58+
4859
export interface TimelineWindowParams {
4960
/** repo-id filter, or null for all repos */
5061
repoIds: number[] | null;
@@ -206,13 +217,25 @@ export function useTimelineWindow(
206217
.then((win) => {
207218
if (qid !== queryRef.current) return;
208219
const cur = windowRef.current;
209-
const merged = [...cur.rows, ...win.rows];
220+
let rows = [...cur.rows, ...win.rows];
221+
let baseIndex = cur.baseIndex;
222+
let startCursor = cur.startCursor;
223+
// Cap the window — evict the rows now far above the viewport.
224+
if (rows.length > MAX_WINDOW) {
225+
const trim = rows.length - MAX_WINDOW;
226+
rows = rows.slice(trim);
227+
baseIndex += trim;
228+
startCursor = cursorOf(rows[0]);
229+
}
210230
windowRef.current = {
211231
...cur,
212-
rows: merged,
232+
rows,
233+
baseIndex,
234+
startCursor,
213235
endCursor: win.endCursor ?? cur.endCursor,
214236
};
215-
setRows(merged);
237+
setRows(rows);
238+
setBaseIndex(baseIndex);
216239
})
217240
.catch(() => {})
218241
.finally(() => {
@@ -232,16 +255,23 @@ export function useTimelineWindow(
232255
.then((win) => {
233256
if (qid !== queryRef.current) return;
234257
const cur = windowRef.current;
235-
const merged = [...win.rows, ...cur.rows];
236-
const newBase = Math.max(0, cur.baseIndex - win.rows.length);
258+
let rows = [...win.rows, ...cur.rows];
259+
const baseIndex = Math.max(0, cur.baseIndex - win.rows.length);
260+
let endCursor = cur.endCursor;
261+
// Cap the window — evict the rows now far below the viewport.
262+
if (rows.length > MAX_WINDOW) {
263+
rows = rows.slice(0, MAX_WINDOW);
264+
endCursor = cursorOf(rows[rows.length - 1]);
265+
}
237266
windowRef.current = {
238267
...cur,
239-
rows: merged,
240-
baseIndex: newBase,
268+
rows,
269+
baseIndex,
241270
startCursor: win.startCursor ?? cur.startCursor,
271+
endCursor,
242272
};
243-
setRows(merged);
244-
setBaseIndex(newBase);
273+
setRows(rows);
274+
setBaseIndex(baseIndex);
245275
})
246276
.catch(() => {})
247277
.finally(() => {

0 commit comments

Comments
 (0)