You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
app: timeline row reconciliation uses Effect deep equality on every stream delta #48434
Every time the session timeline updates, reuseTimelineRows compares each new row against the previous one with Effect's Equal.equals. For a 320 turn session that is 1,288 rows per streamed delta, and each comparison walks the whole row structure, including every message and part ref inside PartGroup. While a reply streams, about a third of JS samples in a profile land in Effect's equal/hash code.
A comparator that switches on the row's _tag and compares only that variant's fields does the same job much faster. Isolated benchmark over a 1,288 row timeline, 1,000 updates per run, one process per build: 11.5 ms per update before, 0.64 ms after. The browser profile shows no samples in Effect equal/hash after the change.
Steps to reproduce
Open a session with 300 or more turns in the web app.
Send a message and let the reply stream.
Record a performance profile while it streams.
The timeline rebuilds and compares all rows on each streamed part delta, so equal/hash samples grow with the stream.
No issue template was used. Please resubmit using one of the three required templates: Bug Report, Feature Request, or Question. For this performance issue, the Bug Report template would be appropriate — it requires a Description field with real content and some context about how to reproduce (e.g., steps, session size, environment info).
Please edit this issue to address the above within 2 hours, or it will be automatically closed.
If you believe this was flagged incorrectly, please let a maintainer know.
Summary of what is verified: the comparator agrees with Equal.equals for every row variant pair as a unit test in the normal CI suite, the isolated reconcile A/B reproduces at 11,527 us to 642 us per reconcile (-94.4%, A/A within 1.7%), and the browser run shows script time and long tasks roughly halved with identical rendered output. Full bytecode and counter evidence is in the PR comment.
Thanks for updating the issue! It now includes a clear description, reproduction steps, version, and environment info. Everything looks good — the needs:compliance label has been removed.
window.log recorded 15 renderer unresponsive events between 2026-09-16 and 2026-09-18, durations 0.93 s – 34.55 s (median approx. 5.8 s, total approx. 116 s).
The captured stack samples land on exactly the path described in this issue:
at structureKeys (main-mfFBJ4oT.js:44196:3)
at structure (main-mfFBJ4oT.js:44201:27)
at hash (main-mfFBJ4oT.js:44137:20)
at compareObjects (main-mfFBJ4oT.js:44268:23)
at Object.equals2 [as equals] (main-mfFBJ4oT.js:139111:12)
at reuseTimelineRows (main-mfFBJ4oT.js:139130:22)
A second cluster of samples lands on message-row construction instead:
at constructMessageRows (main-mfFBJ4oT.js:139242:49)
at Array.flatMap (<anonymous>)
The same renderer.log accumulated roughly 119,186ResizeObserver loop completed with undelivered notifications entries across sessions (cf. #43355).
Timing correlation (freeze-time +/- 25 s window, from the server-side stream log): the 2026-09-18 11:28:25 freeze (1.66 s) happened while 6 sessions / 16 concurrent streams were active.
Note: #48435 does not appear to be merged yet and 1.18.31 does not include the change, so desktop users still hit this path. I can share the raw event dump (JSONL, 15 entries with per-event stack samples) if that helps.
Thanks for the detailed profiling and benchmark, @Priyansh4444! To restate the request: timeline row reconciliation (reuseTimelineRows) should stop using Effect's deep Equal.equals for every row on each streamed delta. Instead it would use a comparator that switches on the row's _tag and compares only that variant's fields, which cuts the per-update cost in long sessions.
I checked current dev, and TimelineRow.equals still delegates to Equal.equals, so this still applies. Since reconciliation gives correct results and this is a speed-up rather than broken behaviour, I've filed it as an improvement. Maintainers will consider it along with your draft PR #48435.
Activity
github-actions commentedon Sep 11, 2026
This issue doesn't fully meet our contributing guidelines.
What needs to be fixed:
Please edit this issue to address the above within 2 hours, or it will be automatically closed.
If you believe this was flagged incorrectly, please let a maintainer know.
Priyansh4444 commentedon Sep 11, 2026
Draft PR with the measured fix: #48435.
Summary of what is verified: the comparator agrees with
Equal.equalsfor every row variant pair as a unit test in the normal CI suite, the isolated reconcile A/B reproduces at 11,527 us to 642 us per reconcile (-94.4%, A/A within 1.7%), and the browser run shows script time and long tasks roughly halved with identical rendered output. Full bytecode and counter evidence is in the PR comment.github-actions commentedon Sep 11, 2026
Thanks for updating the issue! It now includes a clear description, reproduction steps, version, and environment info. Everything looks good — the needs:compliance label has been removed.
cloudcat214 commentedon Sep 19, 2026
Same hotspot reproduced on the Electron desktop client — 15 real freezes over 3 days
Environment: Windows 11 · OpenCode Desktop 1.18.31 (Electron) · renderer bundle
main-mfFBJ4oT.js· third-party OpenAI-compatible provider.window.logrecorded 15renderer unresponsiveevents between 2026-09-16 and 2026-09-18, durations 0.93 s – 34.55 s (median approx. 5.8 s, total approx. 116 s).The captured stack samples land on exactly the path described in this issue:
A second cluster of samples lands on message-row construction instead:
The same
renderer.logaccumulated roughly 119,186ResizeObserver loop completed with undelivered notificationsentries across sessions (cf. #43355).Timing correlation (freeze-time +/- 25 s window, from the server-side stream log): the 2026-09-18 11:28:25 freeze (1.66 s) happened while 6 sessions / 16 concurrent streams were active.
Note: #48435 does not appear to be merged yet and 1.18.31 does not include the change, so desktop users still hit this path. I can share the raw event dump (JSONL, 15 entries with per-event stack samples) if that helps.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed profiling and benchmark, @Priyansh4444! To restate the request: timeline row reconciliation (
reuseTimelineRows) should stop using Effect's deepEqual.equalsfor every row on each streamed delta. Instead it would use a comparator that switches on the row's_tagand compares only that variant's fields, which cuts the per-update cost in long sessions.I checked current
dev, andTimelineRow.equalsstill delegates toEqual.equals, so this still applies. Since reconciliation gives correct results and this is a speed-up rather than broken behaviour, I've filed it as an improvement. Maintainers will consider it along with your draft PR #48435.