Skip to content

feat(dashboard): Open long transcripts at the read position - #2635

Open
sentry-junior[bot] wants to merge 18 commits into
mainfrom
feat/dashboard-open-at-read-position
Open

sentry-junior[bot] wants to merge 18 commits into
mainfrom
feat/dashboard-open-at-read-position

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

First step of #2568: cap how far toward the present the first load goes.

Today a reader whose read position is older than the latest 500 events opens at the latest page. The "New" marker shows at the top of the loaded rows, not at the first unread row, and the reader has to scroll back through every page to get there.

API

  • GET /api/conversations/:id/events takes after=<cursor> and around=<cursor> as well as before=<cursor>. An after page holds the earliest events after the cursor. An around page ends half of its events with the cursor event, and the rest follow it. Both have a nextCursor when newer events follow. An around page also has a previousCursor when older events come before it.
  • Conversation detail has an unreadCursor when the viewer's read position is older than events. It is a normal signed cursor at the read position. It comes from the read position that detail already reads, so detail runs no extra query.
  • GET /api/conversations/:id?unreadPage=true also returns the around page of unreadCursor as unreadPage. Without the flag, or without a cursor, detail reads no extra page.

Dashboard (new transcriptUnreadHistory.ts)

  • When the fetched detail has an unreadCursor, and the reader opens at unread (the default), the transcript starts with the page around the read position, so the last read events show above the marker. It opens at the "New" marker as usual.
  • The first detail fetch of a visit asks for unreadPage, so that page needs no second request. Polls do not ask. If the page does not come with the detail (for example, the fetch joined one that was already in flight), the around request reads it as before.
  • A cached transcript stays on screen, with no marker and no earlier-page loads, until this visit's fetch tells where to open and the unread page is there.
  • Newer pages load when the reader scrolls near the end, the same way as earlier pages. The latest page joins only after they reach it, so the transcript has no gap. Until then, the transcript ends with "Show newer messages", no thinking row, and sends wait in the queue.
  • Earlier pages load on scroll from the read position, not from the latest page.
  • A linked event, Turn, or memory outside the loaded pages loads one page at a time toward it.
  • Without a cursor, with the "Latest message" preference, or when the first page fails, nothing changes.

Server cost

  • Detail polls: no new query. The read position now reads in parallel with the other detail reads, not after them.
  • Open far behind: one request. Detail reads the around page (two half pages in parallel) next to its mention lookup. That is one round trip, one conversation read, and one access read fewer than a separate around request.
  • Each newer page costs the same as an earlier page, and only loads when the reader reaches it. A reader who opens far behind and does not scroll reads one page more than today, in the same request. A reader who reads to the end reads the same pages as today, but from the other direction.

The read-position journeys moved to conversations-read-position.spec.ts, because the mobile spec went over the file length limit. The far-behind journey opens 120 events behind the latest page and checks these things. The reader opens at the first unread row, and the first detail request brings the read events above it, with no /events request. Polls do not ask for the page. No newer page loads before the reader scrolls. The latest page waits until the newer pages reach it. The pages before the latest page are never read. The other read-position journeys get no unreadPage, so they cover the separate around request.

Detail mocks in the other e2e specs now match the detail URL with a query string. The archive queries moved to conversationArchive.ts, so queries.ts stays under the file length limit.

Still open in #2568

Refs #2568

via @BYK.

--

View Session in Sentry

@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
junior-docs Ready Ready Preview Nov 4, 2026 8:38pm UTC

Request Review

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

3 screenshot changes — 3 changed · 0 added · 0 removed

Review screenshots in Frameshift

Automation Run Details · Desktop
Automation Run Details · Desktop
Changed
Automation Run Details Light · Desktop
Automation Run Details Light · Desktop
Changed
Settings Mcp · Desktop
Settings Mcp · Desktop
Changed

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@blacksmith-sh

This comment has been minimized.

When the read position is older than the latest page, Conversation detail
returns an unreadCursor. The events API pages forward with after. The
dashboard opens at the read position, loads newer pages in the background
until they reach the latest page, and loads earlier pages on scroll from
there.

Refs #2568
…ned events

A cache from a visit that read to the end has no unread cursor. The first
fetched detail now supplies it, and the cached transcript stays until the
unread pages load.

Events that new events push out of the latest page now stay in the
transcript, so the latest page does not unjoin after catch-up.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@BYK BYK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GET /api/conversations/:id/events takes after= as well as before=. An after page holds the earliest events after the cursor. It has a nextCursor when newer events follow, and a previousCursor when older events come before it.

We should also probably have an "around=" for this?

…t-read-position

# Conflicts:
#	packages/junior-dashboard/src/client/conversations/ConversationPage.tsx

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

…ng rows

Read the latest page alone, not the transcript joined with cached earlier
pages, and not the deferred copy that lags the fetched read cursor. Withhold
inline sends while newer pages load. Memory jumps use the shown history and
wait for newer pages before they look back.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

The transcript does not end at the latest event until newer pages join, so
an inline send has no place there. Keep it in the composer queue.
@blacksmith-sh

This comment has been minimized.

@BYK BYK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please ensure there are no perf regressions on the server side and ideally there is actually improvements.

Newer pages from the read position loaded one after another until they
reached the latest page, so a reader far behind cost one server page read
per 500 unread events, even without a scroll. Load them when the reader
scrolls near them, like earlier pages. The server now reads only the pages
that the reader reaches.

Linked events, Turns, and memory jumps share one search step: load a page
toward the row, or wait for the page that loads. The unread cursor comes
only from the fetched detail, so the cache no longer needs it.

After pages no longer read one row before the page: nothing uses it.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

…tail

The first detail request of a visit asks for the page around the read
position with unreadPage=true. A reader far behind opens with one request,
not two. Polls do not ask, so the server reads that page once.

The server now reads the read position with the other detail reads, in
parallel. A cached transcript holds without a marker until this visit's
fetch tells where to open.

Move the archive queries to conversationArchive.ts so queries.ts stays
under the file length limit.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@thomasjiangcy

Copy link
Copy Markdown
Contributor

Might wanna rebase this

Jump to latest, a send, or the mobile keyboard now skips the newer pages
that are not loaded. The transcript shows the latest page from the polled
detail at once, and earlier pages load above it as the reader scrolls up.

A transcript that does not end at the latest event no longer follows its
end. A newer page that loads below the reader keeps their place, so pages
no longer load one after another, and the jump control stays visible.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
…t-read-position

# Conflicts:
#	packages/docs/src/content/docs/operate/dashboard.md

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

…led one

A send or the mobile keyboard during a cached reopen now shows the latest
page after the fetch too. The page owns the choice, so it works before the
unread page loads. An explicit pin no longer opens at the "New" marker.

A failed unread page no longer fails conversation detail. The dashboard
then reads it with an around request, which reports the failure.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

The held copy sits at its latest rows while the visit fetches where to
open, so reporting them moved the read position past unread events.

Co-Authored-By: BYK <burak.kaya@sentry.io>
The app shell started its own detail fetch on a direct load. The page's
first fetch joined it and lost ?unreadPage=true, so far-behind readers
paid a second, serial around request. The shell now reads the title
from the cache without fetching.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
sentry-junior Bot and others added 2 commits November 4, 2026 20:23
…t-read-position

# Conflicts:
#	packages/junior-dashboard/src/client/conversations/ConversationPage.tsx
#	packages/junior-dashboard/src/client/conversations/ConversationSidebar.tsx
#	packages/junior-dashboard/src/client/conversations/ConversationWorkspace.tsx
#	packages/junior-dashboard/src/client/conversations/conversationArchive.ts
#	packages/junior-dashboard/src/client/conversations/queries.ts
#	packages/junior/src/api/conversations/detail.ts
#	packages/junior/src/api/conversations/routes.ts
#	packages/junior/src/api/schema.ts

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Co-Authored-By: BYK <burak.kaya@sentry.io>
This reverts commit d156e15. The page mounts only after the dashboard
core data loads, so the shell's early fetch is what runs the detail in
parallel with it. Without it, every direct load waits one more round
trip for the detail, to save an around request only for far-behind
readers. It also made the offline reconnect e2e test flaky.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Co-Authored-By: BYK <burak.kaya@sentry.io>
@blacksmith-sh

blacksmith-sh Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Found 2 test failures on Blacksmith runners:

Failures

Test View Logs
[chromium] › packages/junior-dashboard/e2e/
conversation-send.spec.ts:265:1 › hands web and external messages from queue to history
without gaps
View Logs
packages/junior-dashboard/e2e/conversation-send.spec.ts/
hands web and external messages from queue to history without gaps
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5f119d6. Configure here.

pages: [unreadPage],
},
}
: undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unread pages leak across visits

Medium Severity

The unread newer-page query uses staleTime: Infinity and a key of conversation plus unreadCursor only. A later visit with the same cursor rehydrates every previously fetched newer page, including pages loaded toward a linked event that the reader never viewed, so the capped around page is replaced by the old full range.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5f119d6. Configure here.

This branch was successfully deployed

1 active deployment
Preview – junior-docs — 5f119d61 Deployed Nov 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants