atagen

malice

public
Star0
Watch0

Commit 0f306a02ef99

tarball

atagen <boss@atagen.co>2026-10-27 03:27 UTC

1 file changed+12-4
web: hold the history anchor offset across repeated restores

Each restore re-measured the anchor after its own pixel-snapped correction, so the rounding error carried into the next backfill page and the visible rows crept by a fraction of a pixel per insert until history stopped filling.
Modified file: web/src/lib/timeline-viewport.ts+12 additions−4 deletions
@@ -11,12 +11,19 @@ export class TimelineViewport {
1111 private frame: number | null = null;
1212 private animation: { from: number; to: number; start: number } | null = null;
1313 private initialized = false;
14+ private settledTop: number | null = null;
1415
1516 constructor(readonly element: HTMLElement, readonly roomId: string | null) {
1617 this.capture();
1718 }
1819
1920 capture(): void {
21+ if (this.settledTop !== null && Math.abs(this.element.scrollTop - this.settledTop) < 1) return;
22+ this.recapture();
23+ }
24+
25+ private recapture(): void {
26+ this.settledTop = null;
2027 const bounds = this.element.getBoundingClientRect();
2128 const top = bounds.top + this.element.clientTop;
2229 const bottom = top + this.element.clientHeight;
@@ -47,11 +54,12 @@ export class TimelineViewport {
4754 (anchor.eventId ? rows.find((row) => row.dataset.event === anchor.eventId) : undefined);
4855 if (!row) continue;
4956 this.setTop(this.element.scrollTop + row.getBoundingClientRect().top - top - anchor.offset);
50- this.capture();
57+ this.top = this.element.scrollTop;
58+ this.settledTop = this.top;
5159 return;
5260 }
5361 this.setTop(this.top);
54- this.capture();
62+ this.recapture();
5563 }
5664
5765 scrollToBottom(smooth = false): void {
@@ -61,7 +69,7 @@ export class TimelineViewport {
6169 if (!animate) {
6270 this.releaseScroll();
6371 this.setTop(target);
64- this.capture();
72+ this.recapture();
6573 return;
6674 }
6775 if (this.animation && Math.abs(this.animation.to - target) < 0.5) return;
@@ -95,7 +103,7 @@ export class TimelineViewport {
95103 if (progress < 1) this.frame = requestAnimationFrame(this.advance);
96104 else {
97105 this.animation = null;
98- this.capture();
106+ this.recapture();
99107 }
100108 };