Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: 01M5JPN2QNMG751NXC43TCM3SR
anchor: '@file'
created: 2026-10-22T21:00:43Z
norm: '1'
sig: e0cbdab303939db3
body_hash: 6885f9b9dd17486d
raw_hash: 38eb59c30e3b5a2c
lines: 1-374
---

a quiet period must end a scroll; browsers send no scrollend when they clamp scrollTop after content shrinks
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: '@file'
created: 2026-10-16T23:05:41Z
norm: '1'
sig: ce515648b5bedef2
body_hash: a984910f8f8a9cdc
raw_hash: c0f95564f75550a7
vouched: 2026-10-22T20:42:40Z
body_hash: 3696028d4b1ada45
raw_hash: 67b90ab8452ae3e8
vouched: 2026-10-22T21:00:46Z
lines: 1-912
---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: 01M5JPN2P38553WJGVMF742JC7
anchor: function textBlocks
created: 2026-10-22T21:00:43Z
norm: '1'
sig: 501d68b5e67bcf4e
body_hash: fd0944277f6addb6
raw_hash: 64f19e54a7a5b9a1
lines: 451-496
---

every render with an unparsed chunk must queue its job, even one already in flight, or a cancelled effect leaves the old text on screen
8 changes: 8 additions & 0 deletions packages/ui/src/components/scroll-view/scroll-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { cn } from "../../lib/utils";

const EMPTY: scroll.Frame = { items: [], total: 0 };
const INTENT = 500;
const QUIET = 200;

/** Imperative handle exposed via ref. */
type ScrollViewHandle = {
Expand Down Expand Up @@ -244,14 +245,20 @@ function ScrollView(
project.current(true);
});
};
// Browsers send no scrollend when they clamp scrollTop after content shrinks, so a
// quiet period also ends a scroll; otherwise onScrollEnd never runs.
let quiet: ReturnType<typeof setTimeout> | undefined;
let onscroll = () => {
if (!scrolling.current) {
scrolling.current = true;
started.current?.();
}
clearTimeout(quiet);
quiet = setTimeout(done, QUIET);
schedule();
};
let done = () => {
clearTimeout(quiet);
if (!scrolling.current) return;
scrolling.current = false;
settled.current?.();
Expand Down Expand Up @@ -294,6 +301,7 @@ function ScrollView(

return () => {
active = false;
clearTimeout(quiet);
observer.disconnect();
el.removeEventListener("scroll", onscroll);
el.removeEventListener("scrollend", done);
Expand Down
4 changes: 3 additions & 1 deletion packages/ui/src/components/timeline/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,9 @@ function textBlocks(
return hit;
}

if (!parsed.get(k)?.promise && !seen.has(k) && (!streaming || !stream.has(slot))) {
// Queue in-flight parses too: start() reuses their promise, and this render's tick must not
// depend on an earlier render whose effect may already have been cancelled.
if (!seen.has(k)) {
jobs.push({ key: k, raw: chunk.raw, sig, mentions, plan, compact, streaming, slot });
seen.add(k);
}
Expand Down