Skip to content

perf(dashboard): Read the mailbox with the first conversation detail - #2716

Merged
thomasjiangcy merged 2 commits into
mainfrom
perf/dashboard-detail-mailbox-one-request
Nov 5, 2026
Merged

thomasjiangcy merged 2 commits into
mainfrom
perf/dashboard-detail-mailbox-one-request

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

When a conversation opens with no cached copy, the transcript waits for two requests in a row: /pending-messages, then /api/conversations/:id. #1932 added this order on purpose. Workers commit input before they acknowledge it, so reading the mailbox first means a message that moves into history between the two reads still appears in one of them.

In prod traces, the detail request started only after the mailbox request returned:

  • 3cffecfa… (navigation): mailbox 47→383ms, then detail from 385ms.
  • 0d26d374… (pageload): mailbox 713→856ms, then detail from 861ms.
  • 19019d5f… and 40a68dfc…: the mailbox took 0.86s and 1.8s in the browser, and the detail waited for it.

Change:

  • GET /api/conversations/:id?mailbox=true reads the mailbox first and then history, in one request. It returns the /pending-messages report as mailbox. A request with mailbox=true never returns 304.
  • The client uses it only for a first read with no cached copy. Polls and cached reopens keep the two reads, so the detail can still return 304 (perf(dashboard): Skip rebuilding unchanged conversation detail on polls #2655).
  • The mock reporting API and its hash-based 304 middleware support the flag. A shared e2e helper, withRequestedMailbox, adds the mailbox to mocked detail reports.

Expected impact (inference): opening a conversation saves one round trip plus the server's mailbox read, about 150–350ms in typical traces and up to about 1.8s in the slow ones. Polling cost does not change.

Checks: pnpm typecheck (junior, junior-dashboard), oxlint, prettier, file-length:check, junior pending-messages and detail integration tests, dashboard vitest, and the whole dashboard Playwright suite (87 passed). tests/dashboard-output.test.ts fails in my sandbox on main too, during its Nitro build, so it is not related to this change.

Overlaps: #2657, #2676, #2677, #2678, and #2428 change the conversation page UI. This PR changes only queries.ts data loading and the detail route, so I expect few conflicts. #2657 (catch up on unread activity) may touch the same detail options. This branch is based on main. #2712 (Neon pool size) is independent, and it fixes most of the server-side wait in the slow traces.

via @thomasjiangcy.

--

View Session [Sentry]

A first load with no cached copy read /pending-messages and then the
detail, so the transcript waited for two round trips. The detail route now
takes mailbox=true and reads the mailbox before history in one request,
which keeps the order that prevents a gap. Polls still read the mailbox
apart so the detail can answer 304.

Co-Authored-By: Thomas Jiang <20501235+thomasjiangcy@users.noreply.github.com>
@vercel

vercel Bot commented Nov 5, 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 5, 2026 6:13am UTC

Request Review

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

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

Review screenshots in Frameshift

Conversation Subagents · Desktop
Conversation Subagents · Desktop
Changed
Settings Mcp · Desktop
Settings Mcp · Desktop
Changed
System Cache Share · Desktop
System Cache Share · Desktop
Changed

Guard the no-304 rule for ?mailbox=true in the core route and the mock
reporting middleware, check that non-participants get a redacted mailbox
on the combined read, and serve queued rows with the first e2e detail
read so the queued-message specs do not wait for a poll.

Co-Authored-By: Thomas Jiang <20501235+thomasjiangcy@users.noreply.github.com>
@thomasjiangcy
thomasjiangcy merged commit 5815ff4 into main Nov 5, 2026
44 checks passed
@thomasjiangcy
thomasjiangcy deleted the perf/dashboard-detail-mailbox-one-request branch November 5, 2026 08:02
BYK pushed a commit that referenced this pull request Nov 5, 2026
…ersation read (#2720)

Opening a conversation built the whole detail report twice.
`readConversationDetailEtag`, added in #2655, hashed the viewer's read
position, and the transcript sends `PUT /read-position` soon after it
opens and again as the reader scrolls. Each write changed the version
token. So the next poll got a 200 with the full report, not a 304. That
new token also made `conversationDetailQueryOptions` refresh agents,
memories, watches and Review items.

A prod browser capture of one conversation open showed it:
`pending-messages` → detail (200, 77.8 kB, 1.41s) → `read-position` →
`pending-messages` → detail (200 again, 77.9 kB, 1.44s) → `memories` and
`spend` again.

Change:

- The read position is no longer part of the detail ETag, and that query
no longer runs on each poll. `useMarkConversationRead` already puts the
stored position into the cached detail, so the reader's own client stays
correct.
- Polls now answer 304 after a read, so the cache keeps the
`unreadCursor` from the last full build. `useMarkConversationRead` now
drops `unreadCursor` and `unreadPage` when the new position reaches the
latest page. It uses the same rule as the server (`lastReadSeq + 1 <
firstSeq`). Without this, a second visit in the same session would open
at rows the reader already read. The current visit is not affected,
because `useReadPositionAtOpen` keeps the values from when the
conversation opened.
- The read-position integration scenario now checks that a poll after a
read returns 304.

Trade-off: another tab or device does not see the new read position
until the detail changes or the page reloads. A new page load has no
cache, so it always gets the current position. If the reader is still
behind the latest page after a write, the cached cursor stays at the
older position until the next full build. A reopen in that tab then
loads from slightly earlier rows.

Not checked: the dashboard Playwright read-position spec, because Chrome
is not installed in my sandbox. CI runs it. Mock reporting uses a
content-hash ETag, so the e2e path still rebuilds after a read either
way. `tests/dashboard-output.test.ts` also fails on `main` in my
sandbox.

Overlaps: #2716 changes the first detail read (mailbox in the same
request) in `queries.ts` and the detail route, but not the ETag. #2657
(unread catch-up) touches read-position e2e and the unread flow.
Whichever merges second may need a small rebase.

<!-- junior-request-attribution:start -->
via @thomasjiangcy.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:agent%3Ab48dee8440f2faa8ee84518a2b9ac612 -->

--

[View
Session](https://junior-prod.sentry.dev/conversations/agent%3Ab48dee8440f2faa8ee84518a2b9ac612)
[[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ab48dee8440f2faa8ee84518a2b9ac612/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: Thomas Jiang <20501235+thomasjiangcy@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview – junior-docs — afca6202 Deployed Nov 5, 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.

1 participant